• AtCoder Grand Contest 030题解


    第一次套刷AtCoder

    体验良好

    传送门


    Poisonous Cookies

    cout<<b+min(c,a+b+1);

    Tree Burning

    难度跨度有点大啊

    可以证明当第一次转向之后,接下来每次的方向都和前一次相反

    因为转向后再往相同方向走一定不如初始就往该方向走然后转两次向

    枚举初始往哪个方向走以及走几步,前缀和优化即可

    #include<ctime>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #define rt register int
    #define ll long long
    using namespace std;
    inline ll read(){
        ll x=0;char zf=1;char ch=getchar();
        while(ch!='-'&&!isdigit(ch))ch=getchar();
        if(ch=='-')zf=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return x*zf;
    }
    void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
    void writeln(const ll y){write(y);putchar('
    ');}
    int k,m,n,x,y,z,cnt,ans;ll ret;
    int a[200010];
    ll hz[200010],qz[200010],qzh[200010];
    void calc(){
        for(rt i=n-1;i>=1;i--)hz[i]=hz[i+1]+2ll*(n-i)*a[i];
        qz[1]=qzh[1]=a[1];
        for(rt i=2;i<=n;i++)qz[i]=qz[i-1]+(2ll*i-1)*a[i],qzh[i]=qzh[i-1]+a[i];
        ll ans=0;
        for(rt i=1;i<=n;i++){
            ans+=a[i];int md=i+n>>1; 
            ret=max(ret,ans-hz[md+1]-(qz[md]-qz[i]-(qzh[md]-qzh[i])*2ll*i)+qzh[n]*(n-i-1));
        }
    }
    int main(){
        int L=read();n=read();a[++n]=L;
        for(rt i=1;i<n;i++)a[i]=read();
        for(rt i=n;i>=2;i--)a[i]-=a[i-1];
        calc();reverse(a+1,a+n+1);calc();
        cout<<ret;
        return 0;
    }

    Coloring Torus

    清真构造题

    若$ kleq n$显然可以构一个$ k*k$的正方形,第$ i$行全填$ i$即可

    当$ n < k leq 2n$时,尝试从斜向入手

    发现每个斜行可以交错填写数字,这样能填写的数字就能达到$ 2n$级别了

    #include<ctime>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #define p 1000000007
    #define inv2 500000004
    #define rt register int
    #define ll long long
    using namespace std;
    inline ll read(){
        ll x=0;char zf=1;char ch=getchar();
        while(ch!='-'&&!isdigit(ch))ch=getchar();
        if(ch=='-')zf=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return x*zf;
    }
    void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
    void writeln(const ll y){write(y);putchar('
    ');}
    int k,m,n,x,y,z,cnt,ans[505][505];
    int main(){
        k=read();
        if(k<=500){
            writeln(k);
            for(rt i=1;i<=k;i++)for(rt j=1;j<=k;j++)cout<<i<<" 
    "[j==k];
            return 0;
        }
        writeln(n=500);
        for(rt i=1;i<=n;i++){
            ans[1][i]=i;
            for(rt x=n,y=i%n+1;x!=1;x--,y=y%n+1)ans[x][y]=i;
        }
        for(rt i=n+1;i<=k;i++){
            if(i+i&1)ans[1][i]=i;
            for(rt x=n,y=i%n+1;x!=1;x--,y=y%n+1)if(i+y&1)ans[x][y]=i;
        }
        for(rt i=1;i<=n;i++)for(rt j=1;j<=n;j++)cout<<ans[i][j]<<" 
    "[j==n];
        return 0;
    }

    Inversion Sum

    比B和C都清真多了...

    设$ f[i][j][k]$表示经过$ i$次操作,$ a_j>a_k$的方案数

    容易发现每次操作只对$ O(n)$级别的状态进行了非乘2的修改,其他都只是继承状态然后$ *2$

    不妨把状态改成经过$ i$次操作,每次操作有$ 50\%$概率交换两个数,求逆序对的期望

    滚动数组之后可以$ O(n^2)$解决转移,最后乘上$ 2^q$即可

    #include<ctime>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #define p 1000000007
    #define inv2 500000004
    #define rt register int
    #define ll long long
    using namespace std;
    inline ll read(){
        ll x=0;char zf=1;char ch=getchar();
        while(ch!='-'&&!isdigit(ch))ch=getchar();
        if(ch=='-')zf=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return x*zf;
    }
    void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
    void writeln(const ll y){write(y);putchar('
    ');}
    int k,m,n,x,y,z,cnt,ans;
    int f[3010][3010],g[3010][3010],a[3010];
    int main(){
        n=read();m=read();
        for(rt i=1;i<=n;i++)a[i]=read();
        for(rt i=1;i<=n;i++)
        for(rt j=1;j<=n;j++)if(a[i]>a[j])f[i][j]=1;
        for(rt i=1;i<=m;i++){
            x=read();y=read();
            for(rt j=1;j<=n;j++)g[x][j]=f[x][j],g[j][x]=f[j][x],g[y][j]=f[y][j],g[j][y]=f[j][y];
            f[x][y]=f[y][x]=1ll*(g[x][y]+g[y][x])*inv2%p;
            for(rt j=1;j<=n;j++){
                const int v1=1ll*(g[x][j]+g[y][j])*inv2%p,v2=1ll*(g[j][x]+g[j][y])*inv2%p;
                if(j!=y&&j!=x)f[x][j]=v1,f[j][x]=v2;
                if(j!=x&&j!=y)f[y][j]=v1,f[j][y]=v2;
            }    
        }
    
        int ans=0;
        for(rt i=1;i<n;i++)
        for(rt j=i+1;j<=n;j++)(ans+=f[i][j])%=p;
        for(rt i=1;i<=m;i++)ans=ans*2%p;
        cout<<ans;
        return 0;
    }

    Less than 3

    神仙题!

    我们在两个01串中的所有“01”结构之间连一条红线,“10”结构之间连一条蓝线

    特判$ n leq 2$之后发现两个01串等价的充要条件是这两个01串的红蓝线一一对应

    发现每次改变非边界的数只会导致一条红/蓝线左移一位或右移一位

    改变边界上的数可能会导致生成一条新的红/蓝线或使一条红/蓝线消失不见

    容易发现移动不能使同一位置有多条线且线的颜色始终保持红蓝对应

    我们可以把边界理解为有无限多条颜色交错的红蓝线

    枚举上面的每条红线对应下面的每条红线,红线对答案的贡献就是每组相互对应的线的距离和 蓝线同理

    数据范围允许暴力枚举对应$ O(n^2)$通过此题

    #include<ctime>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #define rt register int
    #define ll long long
    using namespace std;
    inline ll read(){
        ll x=0;char zf=1;char ch=getchar();
        while(ch!='-'&&!isdigit(ch))ch=getchar();
        if(ch=='-')zf=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return x*zf;
    }
    void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
    void writeln(const ll y){write(y);putchar('
    ');}
    int k,m,n,x,y,z,cnt,ans,ret;
    int a[100010],b[100010],t1,t2;
    char c[100010],s[100010];
    int calc(){    
        t1=t2=0;
        for(rt i=1;i<=n;i++)b[++t2]=0;
        for(rt i=1;i<n;i++)if(c[i]=='0'&&c[i+1]=='1')a[++t1]=i;
        for(rt i=1;i<n;i++)if(s[i]=='0'&&s[i+1]=='1')b[++t2]=i;
        for(rt i=1;i<=n;i++)b[++t2]=n;int nowmin=998244353;
        for(rt i=1;i<=t2-n+1;i++){
            int ans=0;
            for(rt j=1;j<i;j++)if(b[j])ans+=b[j];
            for(rt j=0;j<t1;j++)ans+=abs(a[j+1]-b[i+j]);
            for(rt j=t1;b[i+j]!=n;j++)ans+=n-b[i+j];
            nowmin=min(nowmin,ans);
        }
        return nowmin;
    }
    int main(){
        n=read();
        scanf("%s",c+1);scanf("%s",s+1);
        if(n==1&&c[1]!=s[1])writeln(1);
        else if(n==2&&c[1]!=s[1]&&c[1]==c[2]&&s[1]==s[2])writeln(2);
        else {
            ret=calc();
            for(rt i=1;i<=n;i++)c[i]='0'+'1'-c[i],s[i]='0'+'1'-s[i];
            ret+=calc();cout<<ret;        
        }
        return 0;
    }

    Permutation and Minimum

    正推推不出来的DP题启示我们弃疗倒着推

    显然两个位置如果都不是$ -1$就可以扔掉

    设有cnt组两个位置都是$ -1$则最终答案需要乘上$ cnt!$因为这些组可以任意交换位置

    设$ f[i][j][k]$表示已经填了不小于$ i$的所有数,有$ j$个在序列中出现过的数没有被匹配,有$ k$个在原数列中未出现过的数没有被匹配

    假设当前数在原序列出现过则可以匹配一个未出现过的数或自己变成一个未被匹配的数

    不能匹配一个出现过的数的原因是假设相邻两个数都不是-1则要被扔掉

    假设当前数在原序列中未出现过则可以匹配一个未出现过的数,一个出现过的数或自己成为未被匹配的数

    注意如果匹配一个出现过的数有$ j$种匹配方式

    直接$ O(n^3)DP$即可

    #include<ctime>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #define p 1000000007
    #define rt register int
    #define ll long long
    using namespace std;
    inline ll read(){
        ll x=0;char zf=1;char ch=getchar();
        while(ch!='-'&&!isdigit(ch))ch=getchar();
        if(ch=='-')zf=-1,ch=getchar();
        while(isdigit(ch))x=x*10+ch-'0',ch=getchar();return x*zf;
    }
    void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
    void writeln(const ll y){write(y);putchar('
    ');}
    int k,m,n,x,y,z,cnt,ans;
    int a[605];bool vis[605],nt[605];
    int q[605],t;
    int f[605][305][305];
    int main(){
        n=read();
        for(rt i=1;i<=2*n;i++){
            a[i]=read();if(a[i]!=-1)vis[a[i]]=1;
            if(i&1^1){
                if(a[i]==-1&&a[i-1]==-1)cnt++;
                if(a[i]!=-1&&a[i-1]!=-1)nt[a[i]]=nt[a[i-1]]=1;
            }
        }
        for(rt i=1;i<=2*n;i++)if(!nt[i])if(vis[i])q[++t]=1;else q[++t]=0;n=t; 
        //q=1在原数列中  
        f[n][0][0]=1;
        for(rt i=n;i>=1;i--){
            for(rt j=0;j<=n/2;j++)
            for(rt k=0;k<=n/2;k++)if(f[i][j][k]){
                const int v=f[i][j][k];
                if(q[i]){
                    (f[i-1][j+1][k]+=v)%=p;
                    if(k)(f[i-1][j][k-1]+=v)%=p;
                }else {
                    (f[i-1][j][k+1]+=v)%=p;
                    if(k)(f[i-1][j][k-1]+=v)%=p;
                    if(j)(f[i-1][j-1][k]+=1ll*v*j%p)%=p;
                }
            }
        }
        int ans=f[0][0][0];
        for(rt i=2;i<=cnt;i++)ans=1ll*ans*i%p;
        cout<<ans;
        return 0;
    }
  • 相关阅读:
    左偏树——可以标记合并的堆
    主席树——多棵线段树的集合
    [中山市选2011]完全平方数 ——莫比乌斯函数
    决策单调性优化dp
    [NOI2015]寿司晚宴——状压dp
    【[国家集训队]等差子序列】
    线性基——数集压缩自动机
    Java实现 蓝桥杯VIP 算法训练 筛选号码
    BSGS&EXBSGS 大手拉小手,大步小步走
    CRT&EXCRT 中国剩余定理及其扩展
  • 原文地址:https://www.cnblogs.com/DreamlessDreams/p/10478792.html
Copyright © 2020-2023  润新知