AtCoder Grand Contest 060
Preface
那一天,闪总终于想起了被ACG支配的恐惧……
只能说还好Rating不够,这场Unrated打的,写了个A然后B一直挂(一个细节没想到),C数数又数不来
90min后光速跑路推Gal去了
A - No Majority
AGC的A码量都这么大吗,看来是我写SB了
经典的套路,若所有长度为\(2\)或\(3\)的区间都合法,那么最后整个序列就一定合法
因为长度大于\(3\)的不合法区间一定存在不合法的子区间
那么我们直接DP处理即可,设\(f_{i,j,k}\)表示处理完前\(i\)个位置,第\(i\)位填\(j\),第\(i-1\)位填\(k\)的方案数
转移的话如果这一位已经有了就直接填上,没有的话就枚举一下,注意和前面两位不能重复
复杂度\(O(n\times 26^3)\),应该可以优化但是没必要
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005,mod=998244353;
int n,f[N][26][26],ans; char s[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j,p,q; for (scanf("%d%s",&n,s+1),i=2;i<=n;++i)
if (s[i]!='?'&&s[i]==s[i-1]) return puts("0"),0;
for (i=3;i<=n;++i) if (s[i]!='?'&&s[i]==s[i-2]) return puts("0"),0;
if (s[1]!='?'&&s[2]!='?') f[2][s[2]-'a'][s[1]-'a']=1;
else if (s[1]=='?'&&s[2]!='?')
{
for (i=0;i<26;++i) if (i!=s[2]-'a') f[2][s[2]-'a'][i]=1;
} else if (s[1]!='?'&&s[2]=='?')
{
for (i=0;i<26;++i) if (i!=s[1]-'a') f[2][i][s[1]-'a']=1;
} else
{
for (i=0;i<26;++i) for (j=0;j<26;++j) if (i!=j) f[2][i][j]=1;
}
for (i=3;i<=n;++i)
{
if (s[i]!='?')
{
for (p=0;p<26;++p) if (p!=s[i]-'a')
for (q=0;q<26;++q) if (q!=s[i]-'a'&&q!=p)
inc(f[i][s[i]-'a'][p],f[i-1][p][q]);
continue;
}
for (j=0;j<26;++j)
for (p=0;p<26;++p) if (p!=j)
for (q=0;q<26;++q) if (q!=j&&q!=p)
inc(f[i][j][p],f[i-1][p][q]);
}
if (s[n]!='?'&&s[n-1]!='?') ans=f[n][s[n]-'a'][s[n-1]-'a'];
else if (s[n]=='?'&&s[n-1]!='?')
{
for (i=0;i<26;++i) if (i!=s[n-1]-'a') inc(ans,f[n][i][s[n-1]-'a']);
} else if (s[n]!='?'&&s[n-1]=='?')
{
for (i=0;i<26;++i) if (i!=s[n]-'a') inc(ans,f[n][s[n]-'a'][i]);
} else
{
for (i=0;i<26;++i) for (j=0;j<26;++j) if (i!=j) inc(ans,f[n][i][j]);
}
return printf("%d",ans),0;
}
B - Unique XOR Path
首先一个naive的想法,把所求的路径上的所有格子都赋成\(0\),然后考虑哪些其他位置要填别的数
很容易注意到当存在一段RD
或者DR
的时候(即出现了一个“拐角”),拐角所在处就不能填\(0\)了
因此我们有一个策略,找出拐角的个数,然后把每个拐角都赋值上\(2^k\),然后再把所有和这个拐角的下表和相同的位置都赋上同样的值即可
举个例子,当\(n=4,m=4,S=RDRDRD\)时,有(.
是置为\(0\)的元素,为了和路径区分所以不标出):
001.
1002
.200
2.40
因此只要看拐角的个数是不是小于等于\(k\)即可
然后我就很naive地直接这样写了
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,m,k; char s[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; int turn=0; for (scanf("%d%d%d%s",&n,&m,&k,s+1),i=1;i<n+m-2;++i)
if (s[i]!=s[i+1]) ++turn; puts(turn<=k?"Yes":"No");
}
return 0;
}
然后脑抽一时想不到哪里寄了,其实很simple,就是这样会把形如RDR
这样两个本质相同的拐角算成两个不同的
解决的办法也很简单,我们发现要保证本质不同的拐角只要不重复匹配即可(即不出现上面那样一个位置匹配两个的情况)
那么直接贪心地从前往后找到最大的匹配数即可
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,m,k; bool vis[N]; char s[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; int turn=0; for (scanf("%d%d%d%s",&n,&m,&k,s+1),i=2;i<=n+m-2;++i)
if (s[i]!=s[i-1]&&!vis[i-1]) ++turn,vis[i]=vis[i-1]=1;
for (puts(turn<=k?"Yes":"No"),i=1;i<=n+m-2;++i) vis[i]=0;
}
return 0;
}
C - Large Heap
刚开始去看官方的Editoral给我头看痛了也没搞懂怎么做,只能自己闭门造车
然后画了个图之后发现\(U,V\)的位置的特殊性之后突然就有思路了,然后随便写了个DP就过了?
可能当时比赛的时候被B搞得心态爆炸然后根本没仔细看这题吧,当时一点思路都没有的说
回到正题我们不妨考虑这样构造这个二叉树(这道题里的Heap就是个完全二叉树)
从小到大依次加入\(1,2,\cdots,2^n-1\),显然每次加入的点必须在一个已经加入过的点的下方
很容易发现像这样加点,如果先加到点\(U\)则说明\(P_U<P_V\),反之若先加到点\(V\)则说明\(P_U>P_V\)
同时我们发现\(U,V\)在树上的位置很特殊,\(U\)是通过\(A\)条只有向左儿子的边走到的,\(V\)是通过\(B\)条只有向右儿子的边走到的
那么很容易设计一个状态,\(f_{a,b}\)表示从根节点开始,连续向左的边走了\(a\)条,连续向右的边走了\(b\)条时,\(P_U<P_V\)的概率
比如下图\(n=4,A=2,B=3\)的情形,我们一个状态\(f_{1,2}\)表示的就是两边分别走到\(a\)点和\(b\)点时,\(P_U<P_V\)的概率
(其中蓝色的点是确定已经填上数的,绿色的点是填没填不影响答案的,白色的点是没填的)
不难发现我们在计算的时候完全不用管那些绿色的点的状态,而且我们也不关心现在从小到大加到哪个数了
考虑转移的话就是从\(f_{a,b}\)转移到\(f_{a+1,b},f_{a,b+1}\),其中两种选法的概率就是\(a\)在左边方向上的子树的size比上\(b\)在右边方向上的子树的size
比如在上面那种情况下,转移的概率比就是\(3:1\),然后终止状态就是某一边走到目标点的时候
由于要算逆元,复杂度是\(O(n^2\log n)\)的,但是时限有10s随便跑
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
const int N=5005,mod=998244353;
int n,ta,tb,f[N][N],pw[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline int sum(CI x,CI y)
{
return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
return x-y<0?x-y+mod:x-y;
}
inline int DP(CI a,CI b)
{
if (a==ta) return 1; if (b==tb) return 0; if (~f[a][b]) return f[a][b];
int sz1=pw[n-a-1]-1,sz2=pw[n-b-1]-1,dv=quick_pow(sum(sz1,sz2));
return f[a][b]=sum(1LL*sz1*dv%mod*DP(a+1,b)%mod,1LL*sz2*dv%mod*DP(a,b+1)%mod);
}
int main()
{
RI i; for (scanf("%d%d%d",&n,&ta,&tb),pw[0]=i=1;i<=n;++i) pw[i]=sum(pw[i-1],pw[i-1]);
return memset(f,-1,sizeof(f)),printf("%d",DP(0,0)),0;
}
Postscript
刚刚不知好歹地看了眼D的正解CODE,多项式大礼包直接给我打懵逼了
不得不说现在的AGC的质量真的是高,后面的题目就先放一放了(主要是CF要补的题太多了)