AtCoder Grand Contest 055

链接

下饭了,悲。

A. ABC Identity

题意

给定一个 \(ABC\) 构成的序列,将其分为 \(6\) 个子序列,每段都是 \(\text{A}^x\text{B}^x\text{C}^x\)\(\text{C}^x\text{A}^x\text{B}^x\) 或其他置换的形式。保证有解。

题解

事实上手模一下可以发现只要 \(A,B,C\) 数量相等一定有解。

考虑将序列分成 \(3\) 段,每段长度为 \(n\)。每次贪心在第一段选择 \(\text{A}\),第二段选择 \(\text{B}\),第三段选择 \(\text{C}\) 划成序列 \(1\),然后选择 \(\text{ACB}\) 划成序列 \(2\),以此类推。

考虑证明为什么这样是对的:假设最后剩下了一些字符,不妨假设剩下了 \(\text{CBA}\),那么这三个字符一定可以被贪心放入 \(\text{CBA}\) 这个子序列中,其余同理。

所以一定可以构造出解。

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 600010
using namespace std;
char s[N];
int a[N],c[N][3],n,m;
bool cut[N];
int ans[N];
void solve(int x,int y,int z,int id)
{
    int t1=0,t2=0,t3=0;
    for(int i=1;i<=n;i++) if(!cut[i] && a[i]==x) ++t1;
    for(int i=n+1;i<=n*2;i++) if(!cut[i] && a[i]==y) ++t2;
    for(int i=n*2+1;i<=n*3;i++) if(!cut[i] && a[i]==z) ++t3;
    int t=min(t1,min(t2,t3));
    for(int i=1,j=1;j<=t;i++) if(!cut[i] && a[i]==x) cut[i]=true,ans[i]=id,j++;
    for(int i=n+1,j=1;j<=t;i++) if(!cut[i] && a[i]==y) cut[i]=true,ans[i]=id,j++;
    for(int i=n*2+1,j=1;j<=t;i++) if(!cut[i] && a[i]==z) cut[i]=true,ans[i]=id,j++;
}
int main()
{
    scanf("%d%s",&n,s+1);m=n*3;
    for(int i=1;i<=m;i++) a[i]=s[i]-'A';
    int t=0;
    int id[3]={0,1,2};
    do{
        solve(id[0],id[1],id[2],++t);
    }while(next_permutation(id,id+3));
    for(int i=1;i<=m;i++) printf("%d",ans[i]);
    return 0;
}

B. ABC Supremacy

题意

有两个长度为 \(n\)\(ABC\) 序列 \(s,t\),一次变换定义为将 \(\text{ABC,BCA,CAB}\) 的其中一个变为另一个。问 \(s\) 能否通过变换到达 \(t\)

\(|s|\leq 5\times 10^5\)

题解

考虑令 \(a_i=(s_i-\texttt{A}-i)\mod 3\)。可以发现每次操作等价于将三个相邻相同的 \(a_i\) 同时变成另一个 \([0,2]\) 的数字。

这样就是一个经典套路:考虑任意移动三个相邻且相同 \(a_i\) 不会改变其余 \(a_i\) 的相对顺序,所以可以认为这三个 \(a_i\) 被删掉了。

容易发现,贪心删除之后,剩下序列要求全等。直接用一个栈模拟删除过程即可。复杂度 \(O(n)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 500010
using namespace std;
char s[N],t[N];int n;
void wrong(){puts("NO");exit(0);}
void right(){puts("YES");exit(0);}
void check_pre()
{
    int c[3]={0,0,0};
    for(int i=1;i<=n;i++) c[s[i]-'A']++;
    for(int i=1;i<=n;i++) c[t[i]-'A']--;
    if(c[0] || c[1] || c[2]) wrong();
}
void check_all()
{
    for(int i=1;i<=n;i++) if(s[i]!=t[i]) wrong();
    right();
}
int a[N],b[N];
bool ca[N],cb[N];
void check()
{
    int i=1,j=1;
    while(i<=n && j<=n)
    {
        while(ca[i]) i++;
        while(cb[j]) j++;
        if(a[i]!=b[j]) wrong();
        i++,j++;
    }
    right();
}
int ton[N],tp;
int main()
{
    scanf("%d%s%s",&n,s+1,t+1);
    check_pre();
    for(int i=1;i<=n;i++) a[i]=(s[i]-'a'-i%3+3)%3;
    for(int i=1;i<=n;i++) b[i]=(t[i]-'a'-i%3+3)%3;
    for(int i=1;i<=n;i++)
        if(tp>=2 && a[i]==a[ton[tp]] && a[i]==a[ton[tp-1]])
        {
            ca[i]=ca[ton[tp]]=ca[ton[tp-1]]=true;
            tp-=2;
        }
        else ton[++tp]=i;
    tp=0;
    for(int i=1;i<=n;i++)
        if(tp>=2 && b[i]==b[ton[tp]] && b[i]==b[ton[tp-1]])
        {
            cb[i]=cb[ton[tp]]=cb[ton[tp-1]]=true;
            tp-=2;
        }
        else ton[++tp]=i;
    check();
    return 0;
}

C. Weird LIS

题意

问存在多少个长度为 \(n\)\(a\) 数组满足:

  • \(a_i\in[2,m]\)
  • 存在长度为 \(n\) 的排列 \(P\),满足 \(\forall i\in[1,n]\ ,\ P\) 删去 \(P_i\) 后的最长上升子序列为 \(a_i\)

\(n\leq 5000\)

题解

首先 \(a_i\) 的极差不超过 \(1\)。不妨假设 LIS 为 \(X\),那么所有元素都是 \(X\)\(X-1\)

思考什么情况下是合法的:因为 \(X-1\) 必然在 LIS 中,不妨把 \(X-1\) 的位置作为分界点,每个区域内部还可以互相匹配使 LIS \(+1\)

可以发现的是,总有办法使值为 \(X\) 的位置不在任何 LIS 中,对于剩下位置可以令其两两配对使 LIS \(+1\)

换句话说,如果有 \(a\)\(X-1\)\(b\) 对互相配对的位置,最长上升子序列就是 \(a+b\)

但是这样会记重。我们不妨规定:如果出现连续两个不在任何 LIS 中的位置,那么后面不能出现配对。

考虑 dp。令 \(f_{i,j,0/1/2/3/4}\) 表示当前在 \(i\),LIS 为 \(j\),当前状态为 LIS上/需要配对/被配对/不在任何 LIS 中/不能出现配对。

互相转移即可。但是测一测样例会发现 4 3 的情况输出了 \(8\),事实上因为当 \(m=n-1\) 时全部为 \(n-1\) 的情况,我们认为 LIS 为 \(n\) 但事实上所有位置都是 \(n-1\),需要特判(然后赛时就在这里挂掉了)。

复杂度 \(O(n^2)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 5010
using namespace std;
int n,m,mod;
int f[N][N][5];
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int main()
{
    scanf("%d%d%d",&n,&m,&mod);
    if(m==2){puts("1");return 0;}
    f[0][0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++)
        {
            if(j) add(f[i][j][0],f[i-1][j-1][3]);
            if(j) add(f[i][j][0],f[i-1][j-1][2]);
            if(j) add(f[i][j][0],f[i-1][j-1][0]);
            add(f[i][j][1],f[i-1][j][0]);
            add(f[i][j][1],f[i-1][j][2]);
            if(j) add(f[i][j][2],f[i-1][j-1][1]);
            add(f[i][j][3],f[i-1][j][2]);
            add(f[i][j][3],f[i-1][j][0]);
            add(f[i][j][4],f[i-1][j][3]);
            add(f[i][j][4],f[i-1][j][4]);
            if(j) add(f[i][j][4],f[i-1][j-1][4]);
            // printf("%d %d:",i,j);
            // for(int k=0;k<5;k++) printf("%d ",f[i][j][k]);
            // puts("");
        }
    int ans=1;
    for(int i=3;i<=m;i++)
        add(ans,f[n][i][0]),
        add(ans,f[n][i][2]),
        add(ans,f[n][i][3]),
        add(ans,f[n][i][4]);
    if(n==m+1) ans=(ans+1)%mod;
    printf("%d\n",ans);
    return 0;
}

D. ABC Ultimatum

题意

定义一个长度为 \(3n\) 的字符串 \(S\) 是好的,当且仅当 \(S\) 可以被分解为 \(n\) 个子序列,每个子序列都是 \(\text{ABC,BCA,CAB}\) 中的一个。

给定一个 \(3n\) 的残缺字符串(带有 ?),问有多少个字符串是好的。\(n\leq 15\)

题解

\(F_{\text{AB}}(i)\) 表示将前 \(i\) 个字符中的 \(\text{A}\) 的数量减去 \(\text{B}\) 的数量。令 \(M_{\text{AB}}=\max\{F_{\text{AB}}(i)\}\)。同理定义 \(M_{\text{BC}}\)\(M_{\text{CA}}\)

结论\(S\) 合法当且仅当 \(M_{\text{AB}}+M_{\text{BC}}+M_{\text{CA}}\leq n\),且 \(\text{A,B,C}\) 数量均为 \(n\)

必要性:对于合法的分裂,显然 \(M_{\text{AB}}\) 不超过 \(\text{BCA}\) 个数,\(M_{\text{BC}}\) 不超过 \(\text{CAB}\) 个数,\(\text{CA}\) 同理。故对于合法的 \(S\) 一定有 \(M_{\text{AB}}+M_{\text{BC}}+M_{\text{CA}}\leq n\)

充分性:考虑维护三个指针。初始分别指向:第 \(1\)\(\text{A}\),第 \(1+M_\text{AB}\)\(\text{B}\),第 \(1+M_\text{AB}+M_\text{BC}\)\(\text{C}\)。不断匹配然后右移指针,移到最后就循环到开头。因为 \(\text{A}\) 至多向后偏移 \(\text{AB}\),所以 \(\text{A}\) 的指针和 \(\text{B}\) 不会相撞。\(\text{B,C}\) 同理。由于 \(M_{\text{AB}}+M_{\text{BC}}+M_{\text{CA}}\leq n\),所以最后 \(\text{C,A}\) 也不会相撞。所以构造合法。

直接 dp,令 \(f_{i,ab,bc,ca,a,b,c}\) 表示前 \(i\) 个数中,\(M_{\text{AB}},M_{\text{BC}},M_{\text{CA}}\) 数量,\(\text{A,B,C}\) 数量。直接转移 \(O(n^7)\)

可以发现 \(c=i-a-b\),可以省掉一维。复杂度 \(O(n^6)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 17
#define mod 998244353
using namespace std;
int f[N][N][N][N][N],g[N][N][N][N][N];
char s[N*3];
bool check(int x,char c){return s[x]==c || s[x]=='?';}
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int main()
{
    int n;
    scanf("%d%s",&n,s+1);
    int m=n*3;
    g[0][0][0][0][0]=1;
    for(int i=1;i<=m;i++)
    {
        memcpy(f,g,sizeof(f));memset(g,0,sizeof(g));
        for(int ab=0;ab<=n;ab++)
        for(int bc=0;bc<=n;bc++)
        for(int ca=0;ca<=n;ca++)
        if(ab+bc+ca<=n)
            for(int a=0;a<=n;a++)
            for(int b=0;b<=n;b++)
            {
                int dab=max(ab-1,0),dbc=max(bc-1,0),dca=max(ca-1,0),u=f[ab][bc][ca][a][b];
                if(check(i,'A')) add(g[dab][bc+1][ca][a+1][b],u);
                if(check(i,'B')) add(g[ab][dbc][ca+1][a][b+1],u);
                if(check(i,'C')) add(g[ab+1][bc][dca][a][b],u);
            }
    }
    int res=0;
    for(int ab=0;ab<=n;ab++)
    for(int bc=0;bc<=n;bc++)
    for(int ca=0;ca<=n;ca++)
    if(ab+bc+ca<=n) add(res,g[ab][bc][ca][n][n]);
    printf("%d\n",res);
    return 0;
}

E. Set Merging

题意

给定 \(n\) 个集合,第 \(i\) 个集合 \(S_i\) 初始为 \(\{i\}\),每次可以指定一个 \(i\in[1,n-1)\),将 \(S_i\)\(S_{i+1}\) 变成它们的并。问通过若干次操作,使得最后第 \(i\) 个集合大小是 \(a_i\)

题解

考虑构造一个排列 \(p\)。对于每次合并,可以看做是交换 \(p_i\)\(p_{i+1}\)。可以证明给定最后的排列 \(p\),最后结果是固定的。

结论:设集合 \(S_i\) 最终为 \([l_i,r_i]\),其中 \(l_i\)\(p_{[i,n]}\) 中最小值,\(r_i\)\(p_{[1,i]}\) 中最大值。

可以发现 \(l_i,r_i\) 显然需要是单调递增的。并且如果 \(l_i\neq l_{i+1}\),那么有 \(p_i=l_i\)。同理如果 \(r_i\neq r_{i+1}\) 那么有 \(p_{i+1}=r_{i+1}\)

这样可以确定一些位置。我们希望在其他位置填剩下的数,在不影响前缀最大值后缀最小值的情况下逆序对数最小。容易发现,此时顺序放置一定是最优的。如果顺序放置仍然不合法,那么一定不合法。

最后用树状数组求一遍逆序对数即可。复杂度 \(O(n\log n)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=500010;
int l[N],r[N],p[N],n;bool vis[N];
namespace fenwick{
    int a[N];
    void add(int x,int v){for(;x;x-=x&-x) a[x]+=v;}
    int qry(int x){int v=0;for(;x<=n;x+=x&-x) v+=a[x];return v;}
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&l[i],&r[i]);
    for(int i=1;i<n;i++) if(l[i]>l[i+1] || r[i]>r[i+1]){puts("-1");return 0;}
    for(int i=1;i<=n;i++) if(l[i]!=l[i+1]) p[i]=l[i];
    for(int i=1;i<=n;i++) if(r[i-1]!=r[i]){if(p[i] && p[i]!=r[i]){puts("-1");return 0;}p[i]=r[i];}
    for(int i=1;i<=n;i++) if(p[i])
    {
        if(vis[p[i]]){puts("-1");return 0;}
        vis[p[i]]=true;
    }
    int c=1;
    for(int i=1;i<=n;i++) if(!p[i])
    {
        while(vis[c]) ++c;
        p[i]=c,vis[c]=true;
    }
    int mx=0;
    for(int i=1;i<=n;i++){mx=max(mx,p[i]);if(mx!=r[i]){puts("-1");return 0;}}
    mx=n+1;
    for(int i=n;i;i--){mx=min(mx,p[i]);if(mx!=l[i]){puts("-1");return 0;}}
    long long res=0;
    for(int i=1;i<=n;i++) res+=fenwick::qry(p[i]),fenwick::add(p[i],1);
    printf("%lld\n",res);
    return 0;
}

F. Creative Splitting

题意

定义一个长度为 \(k\) 的序列是好的,当且仅当 \(\forall i\in[1,k],a_i\leq i\)

定义一个长度为 \(n\times k\) 的序列是 amazing 的,当且仅当这个序列可以分成 \(k\) 个子序列,使得每个子序列都是好的。

定义 \(f(x,y)\) 表示所有满足 \(a_i\in[1,n\times k]\)\(a_x=y\) 的序列中 amazing 的序列个数。

对于每个 \(x\in[1,n\times k],y\in[1,k]\),输出 \(f(x,y)\)

题解

双射神仙题。

首先考虑如何判定一个数列是不是 amazing 的:考虑将序列翻转,然后子序列也翻转。维护 \(n\) 个序列一开始为空,贪心把当前数字加入可以加的序列中最长的(越长即限制越大),如果有多个选择最靠右的。

考虑将序列长度看做格子,不妨从行方向看,相当于有 \(k\) 行,每次可以选择一行,然后会有一个小球从该行从右往左第一个空位开始往下掉掉到底端。

可以发现的是,“掉落”这个过程其实不影响方案数。忽略这个过程,相当于每次选择一个没有满的行,然后叠一个小球上去。

考虑在这个意义下,限制 \(b_{pos}=val\) 等价于要求第 \(nk-pos+1\) 次放置必须在排序后的第 \(val\) 行进行。假设可以枚举每一行的小球个数 \(x_i\),要求 \(\sum x_i=nk-pos\),设重排方案数为 \(w\),答案即:

\[w(x)\binom{nk-pos}{x_1,\cdots,x_n}\binom{pos-1}{n-x_1,\cdots,n-x_{val}-1,\cdots,n-x_n} \]

\[(nk-pos)!(pos-1)!w(x)\prod_i\frac{1}{x_i!(n-x_i-[i=val])!} \]

枚举 \(val\)。设 \(f_{i,s,y,c}\) 表示当前枚举到 \(x_i\),和为 \(s\)\(x_{i-1}=y\),并且 \(y\) 出现了 \(c\) 次的方案数,枚举 \(x_i\) 是否等于 \(y\) 转移即可。最后对 pos 统计一下贡献即可。

复杂度 \(O(n^2k^4)\)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=22;int n,k,mod;
int ksm(int a,int b=mod-2)
{
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
    return r;
}
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int f[N][N*N][N][N],fac[N*N],inv[N*N],iv[N*N],ans[N*N][N];
void init(int n=N*N-1)
{
    for(int i=fac[0]=inv[0]=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=ksm(fac[i]);
    for(int i=1;i<=n;i++) iv[i]=ksm(i);
}
int main()
{
    scanf("%d%d%d",&n,&k,&mod);
    init();
    for(int v=1;v<=k;v++)
    {
        memset(f,0,sizeof(f));
        f[0][0][0][0]=1;
        for(int i=0;i<k;i++)
            for(int s=0;s<=i*n;s++)
                for(int x=0;x<=n;x++)
                    for(int c=0;c<k;c++) if(f[i][s][x][c])
                    {
                        if(i+1!=v || x!=n)
                        {
                            int v0=1ll*inv[x]*inv[n-x-(i+1==v)]%mod;
                            add(f[i+1][s+x][x][c+1],1ll*f[i][s][x][c]*v0%mod*iv[c+1]%mod);
                        }
                        if(x<n) add(f[i][s][x+1][0],f[i][s][x][c]);
                    }
        for(int p=1;p<=n*k;p++)
            for(int x=0;x<=n;x++)
                for(int c=0;c<=k;c++)
                add(ans[p][v],1ll*fac[n*k-p]*fac[p-1]%mod*f[k][n*k-p][x][c]%mod*fac[k]%mod);
    }
    for(int i=1;i<=n*k;i++,puts(""))
        for(int j=1;j<=k;j++) printf("%d ",ans[i][j]);
    return 0;
}
posted @ 2022-07-07 16:35  Flying2018  阅读(122)  评论(0)    收藏  举报