2019.03.27【SCOI2016】【洛谷P3290】【BZOJ4572】【LOJ2017】围棋(KMP)(轮廓线DP)

洛谷传送门

BZOJ传送门

LOJ传送门


解析:

由于空间存的下并没有用哈希表优化状态转移,不然还能快一点,主要是哈希表的存参后的解码会稍微麻烦一些。

至少匹配一次,这个至少并不好处理,发现总的方案数始终是 3 n m 3^{nm} 3nm,所以我们考虑计算一次都没有匹配的方案数。

对于上一行的状态,我们只关心它有没有匹配完第一行,直接用一个二进制数压一下就行了。

在第一行匹配上的情况下如果当前行匹配上了第二行就要舍弃掉。

明白这些之后我们来定义状态,设 f [ i ] [ j ] [ s ] [ a ] [ b ] f[i][j][s][a][b] f[i][j][s][a][b]表示处理到第 i i i行第 j j j个格子,轮廓线状态为 s s s,当前行匹配模式矩阵第一行可以匹配到 a a a,匹配第二行可以匹配到 b b b,并且之前与模式矩阵没有发生一次匹配的总方案数。

这道题轮廓线定义稍微有点麻烦,假设当前处理到当前行第 j j j个格子(j>c),那么 0 − ( j − c − 1 ) 0-(j-c-1) 0(jc1)的位置上都是表示这一行的位置能否和模式矩阵第一行匹配,而 j − c j-c jc位以及之后的才表示上一行的状态。

明白了这点之后状态转移也就十分方便了。

不过为了知道添加字符后匹配位置的变化,我们需要先对每一个串单独建立一个AC自动机(Trie图),可以直接利用KMP的nxt数组来加速构建。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::cin;
using std::cout;
using std::cerr;

cs int mod=1e9+7;
inline void Inc(int &a,int b){((a+=b)>=mod)?a-=mod:a;}
inline int quickpow(int a,int b,int res=1){
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}

int n,m,c,q,tot,mask;
int f[2][1<<12][7][7],now;
char s1[7],s2[7];
int a1[7],a2[7];
int nxt1[7],nxt2[7];
int tr1[7][3],tr2[7][3];

inline int id(char c){
	switch(c){
		case 'W':return 0;
		case 'B':return 1;
		case 'X':return 2;
	}
}

inline void init(){
	cin>>(s1+1)>>(s2+1);
	for(int re i=1;i<=c;++i)a1[i]=id(s1[i]),a2[i]=id(s2[i]);
	for(int re i=2,j=0;i<=c;++i){
		while(j&&a1[j+1]!=a1[i])j=nxt1[j];
		if(a1[j+1]==a1[i])++j;
		nxt1[i]=j;
	}
	for(int re i=2,j=0;i<=c;++i){
		while(j&&a2[j+1]!=a2[i])j=nxt2[j];
		if(a2[j+1]==a2[i])++j;
		nxt2[i]=j;
	}
	for(int re i=0;i<c;++i)for(int re j=0;j<3;++j){
		int re k=i;
		while(k&&a1[k+1]!=j)k=nxt1[k];
		if(a1[k+1]==j)++k;
		tr1[i][j]=k;
	}
	for(int re i=0;i<c;++i)for(int re j=0;j<3;++j){
		int re k=i;
		while(k&&a2[k+1]!=j)k=nxt2[k];
		if(a2[k+1]==j)++k;
		tr2[i][j]=k;
	}
	memset(f[0],0,sizeof f[0]);
	f[0][0][0][0]=1;now=1;
}

#define pre now^1
inline void solve(){
	init();
	for(int re i=1;i<=n;++i){
		memset(f[now],0,sizeof f[now]);
		for(int re s=0;s<mask;++s)
		for(int re i=0;i<c;++i)
		for(int re j=0;j<c;++j)
		Inc(f[now][s][0][0],f[pre][s][i][j]);
		now=pre;
		for(int re j=1;j<=m;++j){
			memset(f[now],0,sizeof f[now]);
			for(int re s=0;s<mask;++s)
			for(int re a=0;a<c;++a)
			for(int re b=0;b<c;++b)if(f[pre][s][a][b]){
				for(int re k=0;k<3;++k){
					int pa=tr1[a][k],pb=tr2[b][k],S=s;
					if(j>=c&&(S&(1<<j-c)))S^=1<<j-c;
					if(pa==c)S^=1<<j-c,pa=nxt1[c];
					if(pb==c)if(s&(1<<j-c))continue;
					else pb=nxt2[c];
					Inc(f[now][S][pa][pb],f[pre][s][a][b]);
				}
			}
			now=pre;
		}
	}
	int ans=tot;
	for(int re i=0;i<mask;++i)
	for(int re a=0;a<c;++a)
	for(int re b=0;b<c;++b)
	ans=(ans-f[pre][i][a][b]+mod)%mod;
	cout<<ans<<"\n";
} 

signed main(){
	std::ios::sync_with_stdio(false);
	cin>>n>>m>>c>>q;
	mask=1<<(m-c+1),tot=quickpow(3,n*m);
	while(q--)solve();
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值