IOI2020集训队作业-12 (CF613E, AGC033E, AGC022D)

A - CF613E Puzzle LoverSol合法的序列一定是下面这样的形式:其中第二部分要求只能够向上、下、右走。第一部分和第三部分可以用哈希/SA快速判断是否可以匹配,第二部分只能从第iii列走到第i+1i+1i+1列,分三个阶段进行dpdpdp就可以了。考虑第二部分从右往左的情况,我们可以将www翻转然后再计算一次。注意只有第一部分或只有第三部分将会被数两遍(不翻转www...
摘要由CSDN通过智能技术生成

A - CF613E Puzzle Lover

Sol

合法的序列一定是下面这样的形式:

在这里插入图片描述

其中第二部分要求只能够向上、下、右走。

第一部分和第三部分可以用哈希/SA快速判断是否可以匹配,第二部分只能从第 i i i列走到第 i + 1 i+1 i+1列,分三个阶段进行 d p dp dp就可以了。

考虑第二部分从右往左的情况,我们可以将 w w w翻转然后再计算一次。

注意只有第一部分或只有第三部分将会被数两遍(不翻转 w w w和翻转 w w w都会数到),减去多数的即可。

时间复杂度 O ( n m ) O(nm) O(nm)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=2010,mod=1e9+7;
inline void Add(int &x,int y) { x+=y; if(x>=mod) x-=mod; }
const int P=37;
ll hsh[4][N],pw[N];
void calhsh(char *s,int p,int n) {
	for(int i=1;i<=n;++i) hsh[p][i]=(hsh[p][i-1]*P+s[i]-'a')%mod;
}
ll get(int p,int l,int r) {
	if(p==3) return ((hsh[p][l]-hsh[p][r+1]*pw[r-l+1])%mod+mod)%mod;
	return ((hsh[p][r]-hsh[p][l-1]*pw[r-l+1])%mod+mod)%mod;
}
bool cmp(int p1,int l1,int r1,int p2,int l2,int r2) {
	return get(p1,l1,r1)==get(p2,l2,r2);
}
	
int f[N][2][N],ans;
int n,m;
char S[2][N],T[N];
void sol() {
	memset(f,0,sizeof(f));
	for(int i=2;i<=n;++i)
		for(int d=0;d<2;++d)
			for(int j=2;j<=i&&j*2<=m;++j)
				if(cmp(d^1,i-j+1,i,3,1,j)&&cmp(d,i-j+1,i,2,j+1,j*2)) f[i][d][j*2]++;
	for(int i=1;i<=n;++i)
		for(int d=0;d<2;++d) {
			if(T[1]==S[d][i]) f[i][d][1]++;
			if(T[2]==S[d][i]&&T[1]==S[d^1][i]) f[i][d][2]++;
		}
	for(int i=1;i<n;++i)
		for(int d=0;d<2;++d)
			for(int j=1;j<m;++j) if(f[i][d][j]) {
				if(S[d][i+1]==T[j+1]) {
					Add(f[i+1][d][j+1],f[i][d][j]);
					if(j+2<=m&&S[d^1][i+1]==T[j+2])
						Add(f[i+1][d^1][j+2],f[i][d][j]);
				}
			}
	for(int i=0;i<=n;++i) f[i][0][0]=f[i][1][0]=1;
	for(int i=0;i<=n;++i)
		for(int d=0;d<2;++d)
			for(int j=0;j<=m;++j) if(f[i][d][j]&&!((m-j)&1)) {
				int l=m-j>>1;
				if(l>n-i||l==1) continue;
				if(cmp(d,i+1,i+l,2,j+1,j+l)&&cmp(d^1,i+1,i+l,3,j+l+1,m))
					Add(ans,f[i][d][j]);
			}
}
int main() {
	scanf("%s%s%s",S[0]+1,S[1]+1,T+1);
	n=strlen(S[0]+1),m=strlen(T+1);
	if(m==1) {
		int ans=0;
		for(int i=0;i<2;++i)
			for(int j=1;j<=n;++j)
				ans+=S[i][j]==T[1];
		printf("%d",ans);
		return 0;
	}
	if(m==2) {
		int ans=0;
		for(int x=0;x<2;++x)
			for(int y=1;y<=n;++y) if(S[x][y]==T[1]){
				if(S[x^1][y]==T[2]) ans++;
				if(y>1&&S[x][y-1]==T[2]) ans++;
				if(y<n&&S[x][y+1]==T[2]) ans++;
			}
		printf("%d",ans);
		return 0;
	}
	
	pw[0]=1; for(int i=1;i<=max(n,m);++i) pw[i]=pw[i-1]*P%mod;
	calhsh(S[0],0,n);
	calhsh(S[1],1,n);
	calhsh(T,2,m);
	reverse(T+1,T+m+1);
	calhsh(T,3,m);
	reverse(T+1,T+m+1);
	reverse(hsh[3]+1,hsh[3]+m+1);
	
	sol();
	
	swap(hsh[3],hsh[2]);
	reverse(T+1,T+m+1);
	reverse(hsh[3]+1,hsh[3]+m+1);
	reverse(hsh[2]+1,hsh[2]+m+1);
	sol();
	if(m%2==0) {
		int p=m/2;
		for(int i=p;i<=n;++i)
			for(int d=0;d<2;++d) {
				if(cmp(d,i-p+1,i,2,1,p)&&cmp(d^1,i-p+1,i,3,p+1,m)) ans--;
				if(cmp(d,i-p+1,i,3,1,p)&&cmp(d^1,i-p+1,i,2,p+1,m)) ans--;
			}
		ans=(ans%mod+mod)%mod;
	}
	
	printf("%d",ans);
	return 0;
}

B - AGC033E Go around a Circle

Sol

特判掉 S S S中只有一种字符的情况,此时等价于要求圆上不能够有相邻的两个是没有出现过的字符。

假设 S S S的第一个字符是B。设开头有 l l lB,则会有下面的结论:

  1. 圆上不能有连续的两个R
  2. 圆上任意的一段连续的极长的B,其长度必然是奇数,否则这一段中必然有点没有办法走 l l lB之后到达R的旁边。
  3. 圆上任意的一段连续的极长的B,其长度不超过 l + 1 l+1 l+1

对于连续的一段R肯定是在相邻的那一个R上来回走。

考虑后面的连续的B的段,如果这一段的长度是偶数,那么只需要在相邻的那个B来回走就可以;否则就需要跨过相邻的那一段B,这样就会对圆中连续的极长的B的长度产生限制。注意如果这一段后面没有R则不会产生限制。

由于出发点任意,而对于任意一个出发点任意时刻所处的B的连续段是确定的,所以对所有B的段的长度限制都是一样的。

d p dp dp即可。时间复杂度 O ( n ) O(n) O(n)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=2e5+10,mod=1e9+7;
inline void Dec(int &x,int y) { x-=y; if(x<0) x+=mod; }
inline void Add(int &x,int y) { x+=y; if(x>=mod) x-=mod; }
char S[N];
int n,m,f[N];
int main() {
	rd(n),rd(m);
	scanf("%s",S+1);
	char c=S[1];
	int p=1,r=1,L=m;
	
	while(r<=m&&S[r]==c) r++;
	if(r==m+1) {
		if(n==2) { printf("3\n"); return 0; }
		f[0]=1,f[1]=2;
		for(int i=2;i<=n;++i) f[i]=(f[i-1]+f[i-2])%mod;
		printf("%d\n",(f[n-3]+f[n-1])%mod);
		return 0;
	}
	
	L=min(L,(r-p)|1);	
	p=r;
	while(p<=m&&S[p]!=c) p++;
	
	while(1) {
		r=p;
		while(r<=m&&S[r]==c) r++;
		if(r>m) break;
		if((r-p)&1) L=min(L,r-p);
		p=r;
		while(p<=m&&S[p]!=c) p++;
	}
	int sum[2]={0,0};
	f[1]=1,sum[1]=1;
	for(int i=2;i<=n;++i) {
		f[i]=sum[i&1];
		if(i-L-1>=1) Dec(sum[i&1],f[i-L-1]);
		Add(sum[i&1],f[i]);
	}
	int ans=0;
	for(int i=1;i<=n;++i) {
		if(n-i>L||!((n-i)&1)) continue;
		Add(ans,f[i]*(ll)(n-i+1)%mod);
	}
	printf("%d",ans);
	return 0;
}
/*
6 7
RRBBBBR
*/

C - AGC021F Trinity

Sol

f [ k ] [ p ] f[k][p] f[k][p]表示现在有 k k k p p p行,每一行至少有一个格子是黑色的(可以有没有格子的列),可能的 ( A , B , C ) (A,B,C) (A,B,C)的种数。

枚举第一个黑格子在 k + 1 k+1 k+1列的行的数量 q q q

如果 q = 0 q=0 q=0,那么 f [ k + 1 ] [ p + q ] + = f [ k ] [ p ] × ( ( p + 1 2 ) + 1 ) f[k+1][p+q]+=f[k][p]\times ({p+1\choose 2}+1) f[k+1][p+q]+=f[k][p]×((2p+1)+1)

如果 q > 0 q>0 q>0,不妨考虑在第 k + 1 k+1 k+1列的最上面和最下面各加一个格子,然后从这 p + q + 2 p+q+2 p+q+2行中,选 q + 2 q+2 q+2行涂黑。如果涂了最上面/下面的新加的格子,那么 B , C B,C B,C就对应的是新加的行中最上面的/最下面的那个黑格子;否则就是对应选出来涂黑的最上面/下面的格子。这样算出来的系数和原问题等价。所以我们得到 f [ k + 1 ] [ p + q ] + = f [ k ] [ p ] ( p + q + 2 q + 2 ) f[k+1][p+q]+=f[k][p]{p+q+2\choose q+2} f[k+1][p+q]+=f[k][p](q+2p+q+2)

对于 q > 0 q>0 q>0转移是卷积的形式,可以用 F F T FFT FFT优化,复杂度 O ( n m log ⁡ n ) O(nm\log n) O(nmlogn)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int mod=998244353;
const int N=(1<<14)+10;
int Pow(int x,int y) {
	int res=1;
	while(y) {
		if(y&1) res=res*(ll)x%mod;
		x=x*(ll)x%mod,y>>=1;
	}
	return res;
}
int wn[2][N];
void getwn(int l) {
	for(int i=1;i<(1<<l);i<<=1) {
		int w0=Pow(3,(mod-1)/(i<<1)),w1=Pow(3,mod-1-(mod-1)/(i<<1));
		wn[0][i]=wn[1][i]=1;
		for(int j=1;j<i;++j)
			wn[0][i+j]=wn[0][i+j-1]*(ll)w0%mod,
			wn[1][i+j]=wn[1][i+j-1]*(ll)w1%mod;
	}
}
int rev[N];
void getr(int l) { for(int i=1;i<(1<<l);++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1); }
void FFT(int *A,int len,int f) {
	for(int i=0;i<len;++i) if(rev[i]<i) swap(A[i],A[rev[i]]);
	for(int l=1;l<len;l<<=1)
		for(int i=0;i<len;i+=(l<<1))
			for(int k=0;k<l;++k) {
				int t1=A[i+k],t2=A[i+l+k]*(ll)wn[f][l+k]%mod;
				A[i+k]=(t1+t2)%mod;
				A[i+l+k]=(t1-t2+mod)%mod;
			}
	if(f==1) for(int inv=Pow(len,mod-2),i=0;i<len;++i) A[i]=A[i]*(ll)inv%mod;
}
int A[N],B[N];
int f[210][8010];
int fac[8010],inv[8010];
void getfac(int n) {
	fac[0]=1; for(int i=1;i<=n;++i) fac[i]=fac[i-1]*(ll)i%mod;
	inv[n]=Pow(fac[n],mod-2); for(int i=n;i>=1;--i) inv[i-1]=inv[i]*(ll)i%mod;
}
int C(int n,int m) { return fac[n]*(ll)inv[m]%mod*inv[n-m]%mod; }
int n,m;
int main() {
	rd(n),rd(m);
	getfac(n+2),getwn(14);
	int len=1,cnt=0; while(len<=n*2) len<<=1,cnt++; getr(cnt);
	
	for(int i=1;i<=n;++i) B[i]=inv[i+2];
	FFT(B,len,0);
	
	f[0][0]=1;
	for(int i=1;i<=m;++i) {
		for(int j=0;j<=n;++j) A[j]=f[i-1][j]*(ll)inv[j]%mod;
		for(int j=n+1;j<len;++j) A[j]=0;
		FFT(A,len,0);
		for(int j=0;j<len;++j) A[j]=A[j]*(ll)B[j]%mod;
		FFT(A,len,1);
		for(int j=0;j<=n;++j) f[i][j]=A[j]*(ll)fac[j+2]%mod;
		for(int j=0;j<=n;++j) f[i][j]=(f[i][j]+f[i-1][j]*(ll)(C(j+1,2)+1))%mod;
	}
	int ans=0;
	for(int i=0;i<=n;++i) ans=(ans+f[m][i]*(ll)C(n,i)%mod)%mod;
	printf("%d",ans);
	return 0;
}
A,len,0);
		for(int j=0;j<len;++j) A[j]=A[j]*(ll)B[j]%mod;
		FFT(A,len,1);
		for(int j=0;j<=n;++j) f[i][j]=A[j]*(ll)fac[j+2]%mod;
		for(int j=0;j<=n;++j) f[i][j]=(f[i][j]+f[i-1][j]*(ll)(C(j+1,2)+1))%mod;
	}
	int ans=0;
	for(int i=0;i<=n;++i) ans=(ans+f[m][i]*(ll)C(n,i)%mod)%mod;
	printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值