【洛谷3290】[SCOI2016] 围棋(状压DP+FWT)

点此看题面

大致题意: 一张\(n\times m\)的棋盘,每个格子有\(0/1/2\)三种填法。给出\(q\)\(2\times c\)的模板,问对于每个模板有多少种棋盘包含它。

前言

本题正解是插头\(DP\),然而我想了不到十分钟就用状压\(DP\)+\(FWT\)的非正解过了此题。。。

正难则反

包含一个模板的棋盘数不好算,因此我们可以逆向思维,求出不包含该模板的棋盘数,然后用\(3^{n\times m}\)减去即为所求答案。

这应该是做这种题目一个最基本的套路。

状态设置

考虑一行对于下一行的贡献,我们只需要知道对于该行的每一个格子,是否可以作为模板第一行的结尾。

因此,设\(f_{i,j}\)表示\(DP\)到第\(i\)行,且第\(i\)行每个格子作为模板第一行结尾的可行性状压结果为\(j\)的方案数。

然后我们自然是去枚举每一行\(3^m\)种填法,求出它作为第一行结尾的可行性状压\(T1\)和第二行结尾的可行性状压\(T2\)(每种填法的\(T1\)\(T2\)可以先预处理好,而这一过程还需要用到\(KMP\))。

一个模板没有出现过,也就是上一行与当前行不能存在同一列分别可以作为模板第一行和第二行的结尾。

因此,可以就给\(f_{i,T1}\)加上:

\[\sum_{j∩T2=\emptyset}f_{i-1,j} \]

也就是:

\[\sum_{j\subseteq∁T2}f_{i-1,j} \]

那么只要在每一行\(DP\)完后用\(FWT\)处理一下,就可以实现\(O(1)\)转移了。

总复杂度\(O(nq3^m)\),看起来很不对劲,实际上也的确很不对劲,但终究还是能过的。

代码

#pragma GCC optimize(2)
#pragma GCC optimize("inline")
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define M 12
#define C 6
#define S 531441
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,c,P,Qt,f[N+5][1<<M],T1[S+5],T2[S+5],g1[C+5],g2[C+5];char s1[C+5],s2[C+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
I void KMP(char *s,int *nxt)//KMP预处理Next数组
{
	for(RI i=2,j=0;i<=c;nxt[i]=(j+=s[j+1]==s[i]),++i) W(j&&s[j+1]^s[i]) j=nxt[j];
}
I int Get(char *s,int *nxt,RI p,CI x)//求出填x后的匹配情况
{
	W(p&&s[p+1]^x) p=nxt[p];return p+(s[p+1]==x);//跳Next数组
}
I void FWT(int *s)//FWT
{
	for(RI i=1,j,k;i^P;i<<=1) for(j=0;j^P;j+=i<<1) for(k=0;k^i;++k) Inc(s[i+j+k],s[j+k]);
}
I void Init(int *T,char *s,int *nxt,CI t=0,CI i=0,CI x=0,CI G=0)//预处理每种填法的T1和T2
{
	#define Try(u,v) p=Get(s,nxt,x,v),Init(T,s,nxt,t*3+(u),i+1,p,G|((p==c)<<i))//当前位填v时时的转移
	if(i==m) return (void)(T[t]=G);RI p;Try(0,'B'),Try(1,'W'),Try(2,'X');//判终止情况,否则枚举填什么
}
int main()
{
	RI i,j,t=1;scanf("%d%d%d%d",&n,&m,&c,&Qt);
	for(i=1;i<=m;++i) t*=3;for(P=1<<m,i=0;i^P;++i) f[0][i]=1;W(Qt--)
	{
		scanf("%s%s",s1+1,s2+1),KMP(s1,g1),KMP(s2,g2);
		for(Init(T1,s1,g1),Init(T2,s2,g2),i=1;i<=n;FWT(f[i]),++i)//每DP完一行要做FWT
			for(memset(f[i],0,sizeof(f[i])),j=0;j^t;++j) Inc(f[i][T1[j]],f[i-1][(P-1)^T2[j]]);//枚举当前行填法转移
		printf("%d\n",(QP(3,n*m)-f[n][P-1]+X)%X);//输出总方案数减不合法方案数
	}return 0;
}
posted @ 2020-06-18 19:23  TheLostWeak  阅读(22)  评论(0编辑  收藏