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('\n');}
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

清真构造题

若$ k\leq 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('\n');}
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<<" \n"[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]<<" \n"[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('\n');}
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('\n');}
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('\n');}
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;
}

 

posted @ 2019-03-05 19:15  Kananix  阅读(175)  评论(0编辑  收藏

Contact with me