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要补的题太多了)

posted @ 2022-12-26 21:37  空気力学の詩  阅读(96)  评论(0编辑  收藏  举报