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
l个B
,则会有下面的结论:
- 圆上不能有连续的两个
R
。 - 圆上任意的一段连续的极长的
B
,其长度必然是奇数,否则这一段中必然有点没有办法走 l l l次B
之后到达R
的旁边。 - 圆上任意的一段连续的极长的
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;
}