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\),答案即:
即
枚举 \(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;
}

浙公网安备 33010602011771号