[SCOI2016]围棋,洛谷P3290,kmp辅助轮廓线Dp

本文探讨了在给定的二维网格上进行模板匹配的问题,利用动态规划(DP)和Knuth-Morris-Pratt算法(KMP)进行高效的状态转移,通过滚动数组优化空间复杂度。文章详细介绍了算法实现过程,包括状态定义、状态转移方程及代码实现。

正题

     考虑构造Dp:f[i][j][S][k][l]表示当前在第i行,第j列的轮廓线上,上一行匹配成功的结束端点集合为S,当前这行匹配k了模板第一行的k个,第二行的l个

     考虑到集合S实际上只用记录有效的位置,也就是pos>=c,那么总状态数就是n*m*2^{m-c+1}*c^2

     转移枚举后一个位置放什么,用kmp来找第i个位置后面放一个x会转移到哪里,kmp的时间复杂度显然正确

     若当前第二行新加进来一个字符满足第二行匹配c位,且S中第一位为1,那么这个状态就不合法,直接跳过.

     否则也要更新S,将第一位弹掉,根据新的k来判断当前是否为一个合法的位置.

     行转换的时候更新一下状态,使用滚动数组优化空间

总结

     一开始居然看错题想了大半天,后来发现q个询问之间并没有关系,考试一定要看清楚题!

     kmp,ACAM来维护匹配状态转移还是比较常见的,Dp这一块要找时间多做点题!

#include<bits/stdc++.h>
using namespace std;

int dp[110][13][1<<10][7][7];
int fail[2][10];
char s[2][10];
char w[3]={'W','B','X'};
int n,m,c,q,M,tot;
int mod=1e9+7;

void add(int&x,int y){x=x+y>=mod?x+y-mod:x+y;}

int ksm(int x,int t){
	int tot=1;
	while(t){
		if(t&1) tot=1ll*tot*x%mod;
		x=1ll*x*x%mod;
		t/=2;
	}
	return tot;
}

void solve(){
	for(int k=0;k<2;k++){
		scanf("%s",s[k]+1);
		for(int i=1;i<=c;i++){
			int now=fail[k][i-1];
			while(now!=-1 && s[k][now+1]!=s[k][i]) now=fail[k][now];
			fail[k][i]=now+1;
		}
	}
	int now=0;
	memset(dp[now],0,sizeof(dp[now]));
	dp[now][0][0][0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++)
			for(int S=0;S<M;S++)
				for(int k=0;k<=c;k++)
					for(int l=0;l<=c;l++) if(dp[now][j][S][k][l])
						for(int x=0;x<3;x++){
							int nk=k,nl=l,nS=S;
							while(nk!=-1 && s[0][nk+1]!=w[x]) nk=fail[0][nk];nk++;
							while(nl!=-1 && s[1][nl+1]!=w[x]) nl=fail[1][nl];nl++;
							if((S&1) && nl==c) continue;
							if(j+1>=c){
								nS>>=1;
								nS|=(nk==c)*(M>>1);
							}
							add(dp[now][j+1][nS][nk][nl],dp[now][j][S][k][l]);
						}
		memset(dp[now^1],0,sizeof(dp[now^1]));
		for(int S=0;S<M;S++){
			for(int k=0;k<=c;k++)
				for(int l=0;l<=c;l++)
					add(dp[now^1][0][S][0][0],dp[now][m][S][k][l]);
		}
		now^=1;
	}
	int ans=tot;
	for(int S=0;S<M;S++) add(ans,mod-dp[now][0][S][0][0]);
	printf("%d\n",ans);
}

int main(){
	scanf("%d %d %d %d",&n,&m,&c,&q);M=(1<<(m-c+1));tot=ksm(3,n*m);
	fail[0][0]=fail[1][0]=-1;
	while(q--) solve();
}

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值