AtCoder Grand Contest 030題解

第一次套刷AtCoderios

體驗良好git

傳送門數組


Poisonous Cookies

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

Tree Burning

難度跨度有點大啊優化

能夠證實當第一次轉向以後,接下來每次的方向都和前一次相反spa

由於轉向後再往相同方向走必定不如初始就往該方向走而後轉兩次向code

枚舉初始往哪一個方向走以及走幾步,前綴和優化便可blog

#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$便可get

當$ n < k \leq 2n$時,嘗試從斜向入手string

發現每一個斜行能夠交錯填寫數字,這樣能填寫的數字就能達到$ 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;
}
相關文章
相關標籤/搜索