CWOI2019 暑假提高组模拟题十二

本应$280$的,结果打表太大,拒绝评测,只有$180$。

Problem A 旅行

题面

现在有一段不平整的路,第$i$段的高度为$a_i$。你可以花费$|a_i-k|$,使第$i$段的高度变为$k$,使$\sum_{i=1}^{n-1}|a_i-a_{i+1}|$最小。

题解

贪心地从左到右扫一遍,枚举第$i$段路。如果要使得从$i-1$到$i+1$的体力消耗最小,那么就要两端距离间的差距最小。

  • 如果$a_{i-1}$和$a_{i+1}$都小于$a_i$,那么就将$a_i$改变为$max(a_{i-1},a_{i+1})$。
  • 如果$a_{i-1}$和$a_{i+1}$都大于$a_i$,那么就将$a_i$改变为$min(a_{i-1},a_{i+1})$。

代码

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int MAXN=100005;
int main(){
#ifndef FILE_IN
    freopen("travel.in","r",stdin);
#endif
#ifndef FILE_OUT
    freopen("travel.out","w",stdout);
#endif
    ios::sync_with_stdio(false);
    int n;
    int a[MAXN]={0};
    long long ans=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=2;i<n;i++){
        if((a[i-1]>a[i]&&a[i]<a[i+1])){
            if(a[i-1]>a[i+1]){
                ans+=a[i+1]-a[i];
                a[i]=a[i+1];
            }
            else{
                ans+=a[i-1]-a[i];
                a[i]=a[i-1];
            }
        }
        else if(a[i-1]<a[i]&&a[i]>a[i+1]){
            if(a[i-1]>a[i+1]){
                ans+=a[i]-a[i-1];
                a[i]=a[i-1];
            }
            else{
                ans+=a[i]-a[i+1];
                a[i]=a[i+1];
            }
        }
    }
    for(int i=1;i<n;i++){
        ans+=abs(a[i]-a[i+1]);
    }
    cout<<ans<<endl;
    return 0;
}

Problem B 石头剪刀布

题面

小菜给出一个自然数$n$,小明和小头轮流操作,每次操作可以把$n$减去$n$所拥有的数字中的最大值或者最小值(不包括0),不能操作者输(即$n=0$ 时)。

试求双方最优策略时的胜负情况。(小明先手)

题解

递推。$$fuck_x=(!fuck_{x-findmax(x)})||(!fuck_{x-findmin(x)})$$

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN=1000005;
bool fuck[MAXN]={0,1,1,1,1,1,1,1,1,1};
short findmin(int x){
    short ans;
    do{
        ans=x%10;
        x/=10;
    }
    while(x!=0&&ans==0);
    while(x){
        if(x%10<ans&&x%10>0){
            ans=x%10;
        }
        x/=10;
    }
    return ans;
}
short findmax(int x){
    short ans;
    do{
        ans=x%10;
        x/=10;
    }
    while(x!=0&&ans==0);
    while(x){
        if(x%10>ans&&(x%10)){
            ans=x%10;
        }
        x/=10;
    }
    return ans;
}
void dfs(int x){
    fuck[x]=(!fuck[x-findmax(x)])||(!fuck[x-findmin(x)]);
}
int main(){
    ios::sync_with_stdio(false);
    for(int i=10;i<MAXN;i++){
        dfs(i);
    }
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        int q;
        cin>>q;
        if(fuck[q]){
            cout<<"YES"<<endl;
        }
        else{
            cout<<"NO"<<endl;
        }
    }
    return 0;
}

Problem C 环中环

题面

$n$个整数围成一个环,选出其中的若干数,使得选中的数所组成的中,两个相邻数的差的绝对值不等于$1$。在满足这个前提下,问最多能取多少个数。

题解

动态规划。设$f_i$表示以$i$为终点时最多能取多少个数。$$f_i=f_j+1(abs( a_i-a_j) \neq 1)$$

由于题目要求是环形,因此可以将原数列复制一份接在后面,处理完后答案除以$2$即可。

我们发现$(abs( a_ i-a_ j ) \neq 1)$的数最多只有两个,所以我们只要记录$3$个不同的$a_i$所对应的最大的$3$个$f$的值,直接转移即可。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, a[200001], f[200001], tr[5000000], zl;
void find( int z, int x, int y, int l, int r ) {
    if ( ( x == l ) && ( y == r ) )
        zl = max( zl, tr[z] );
    else {
        int mid = ( x + y ) / 2;
        if ( r <= mid )
            find( z * 2, x, mid, l, r );
        else {
            if ( l > mid )
                find( z * 2 + 1, mid + 1, y, l, r );
            else {
                find( z * 2, x, mid, l, mid );
                find( z * 2 + 1, mid + 1, y, mid + 1, r );
            }
        }
    }
}
int findx( int x, int y ) {
    zl = 0;
    if ( x >= 0 && y >= 0 )
        find( 1, 0, 1100000, x, y );
    else
        return ( 0 );
    return ( zl );
}
void change( int z, int x, int y, int l ) {
    if ( x == y )
        tr[z] = zl;
    else {
        int mid = ( x + y ) / 2;
        if ( l <= mid )
            change( z * 2, x, mid, l );
        else
            change( z * 2 + 1, mid + 1, y, l );
        tr[z] = max( tr[z * 2], tr[z * 2 + 1] );
    }
}
int main() {
    ios::sync_with_stdio( false );
    cin >> n;
    int i, j, k;
    for ( int i = 1; i <= n; i++ ) {
        cin >> a[i];
        a[i + n] = a[i];
    }
    memset( tr, 0, sizeof( tr ) );
    for ( int i = 1; i <= 2 * n; i++ ) {
        f[i] = max( findx( a[i] + 2, 1100000 ), max( findx( 0, a[i] - 2 ), findx( a[i], a[i] ) ) );
        zl = f[i] + 1;
        f[i] += 1;
        change( 1, 0, 1100000, a[i] );
    }
    cout << ( tr[1] >> 1 ) << endl;
    return 0;
}

Problem D 寻找神格

题面

维护一个序列,使之支持下列操作:

  • 一个区间增加一个定值。
  • 求指定区间方差。
  • 求指定区间和。

题解

分块。

代码

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef long double ld;

const int N=100010;
const int M=350;
int n,Q,T,L[M],R[M],pos[N];
ll ans[M],add[M],sum[M],a[N],Read,f,x,y,z;
char ch;

ll read(){
    Read=0;f=1;ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-f;ch=getchar();}
    while (ch>='0'&&ch<='9')Read=(Read<<3)+(Read<<1)+ch-48,ch=getchar();
    return Read*f;
}
void change(int l,int r,ll z)  //修改
{
    int q=pos[l],p=pos[r];
    if (q==p)  //一个块暴力修改
    {
        for (register int i=l;i<=r;i++)
            ans[q]+=2*(a[i]+add[q])*z+z*z,a[i]+=z,sum[q]+=z;
        return;
    }
    for (register int i=l;i<=R[q];i++)
        ans[q]+=2*(a[i]+add[q])*z+z*z,a[i]+=z,sum[q]+=z;
    for (register int i=L[p];i<=r;i++)  //分开
        ans[p]+=2*(a[i]+add[p])*z+z*z,a[i]+=z,sum[p]+=z;
    for (register int i=q+1;i<p;i++)  //lazy修改
        add[i]+=z,ans[i]+=2*sum[i]*z+z*z*(R[i]-L[i]+1),sum[i]+=z*(R[i]-L[i]+1);
}
ll ask1(int l,int r)  //询问和
{
    int q=pos[l],p=pos[r];
    ll s=0;
    if (q==p){
        for (register int i=l;i<=r;i++) s+=(ll)(a[i]+add[q]);  //暴力求和
        return s;
    }
    for (register int i=l;i<=R[q];i++) s+=(ll)(a[i]+add[q]);
    for (register int i=L[p];i<=r;i++) s+=(ll)(a[i]+add[p]);
    for (register int i=q+1;i<p;i++) s+=sum[i];
    return s;
}
ld ask2(int l,int r)  //询问方差
{
    int q=pos[l],p=pos[r];
    ll Ans=0,Sum=0;
    if (q==p){
        for (register int i=l;i<=r;i++)  //暴力
            Sum+=a[i]+add[q];
        ld Ave=(ld)Sum/(ld)(r-l+1),s=0.0;
        for (register int i=l;i<=r;i++)
            s+=((ld)a[i]+(ld)add[q]-Ave)*((ld)a[i]+(ld)add[q]-Ave);
        return (ld)s/(ld)(r-l+1);
    }
    for (register int i=l;i<=R[q];i++)  //左边
    {
        Ans+=(a[i]+add[q])*(a[i]+add[q]);
        Sum+=a[i]+add[q];
    }
    for (register int i=L[p];i<=r;i++)  //右边
    {
        Ans+=(a[i]+add[p])*(a[i]+add[p]);
        Sum+=a[i]+add[p];
    }
    for (register int i=q+1;i<p;i++)  //中间直接利用lazy
    {
        Ans+=ans[i];
        Sum+=sum[i];
    }
    ld Ave=(ld)Sum/(ld)(r-l+1);
    return ((ld)Ans-2.0*(ld)Sum*Ave+(ld)(r-l+1)*Ave*Ave)/(ld)(r-l+1);
    //完全平方公式输出
}

int main()
{ 
    freopen("find.in","r",stdin);
    freopen("find.out","w",stdout);
    n=read(),Q=read();
    for (register int i=1;i<=n;i++)
        a[i]=read();
    T=sqrt(n);
    if (T*T<n) T++;
    for (register int i=1;i<=T;i++){
        L[i]=R[i-1]+1;
        R[i]=min(i*T,n);
        for (register int j=L[i];j<=R[i];j++){
            pos[j]=i;
            ans[i]+=a[j]*a[j];
            sum[i]+=a[j];
        }
    }
    while (Q--){
        x=read();
        if (x==0){
            x=read(),y=read();
            change(x,x,y);
        }
        else if (x==1){
            x=read(),y=read(),z=read();
            change(x,y,z);
        }
        else if (x==2){
            x=read(),y=read();
            printf("%lld\n",ask1(x,y));
        }
        else if (x==3){
            x=read(),y=read();
            printf("%0.3Lf\n",ask2(x,y));
        }
    }
    return 0;
}
Last modification:August 22nd, 2019 at 07:50 pm
如果您觉得我的文章有用,请赏一颗糖糖。

Leave a Comment