Luogu P3290 [SCOI2016]围棋
ORZ陈指导花式切题,奶一口今年陈指导必进队!
虽然这题是插头DP的练习题,但是数据太水我们仍然可以直接状压水过去
首先考虑我们只需要求出一个都不匹配的方案数然后减一下就好了,所以我们考虑怎么算不匹配的方案数
相信大家都注意到了只有两行这个条件,再加上数据范围很小,我们先考虑一行的情况
设\(g_{0/1,s}\)表示\(s\)(三进制状压表示这一行怎么填)和模板串第\(0/1\)行匹配的情况(二进制状压是否成功)
然后记\(f_{i,s}\)表示多行的情况,考虑每次转移两行不能有同位置的\(1\)对上,因此方程为:
\[f_{i,s}=\sum_{t\cap s=\emptyset} f_{i-1,t}\Leftrightarrow f_{i,s}=\sum_{t\in \complement s} f_{i-1,t}
\]
然后我们注意到那个子集卷积显然可以用FWT的或卷积优化,于是就做完了
复杂度\(O(n\times q\times 3^m)\),有点卡但是还是能过
#include<cstdio>
#include<cstring>
#define RI int
#define CI const int&
using namespace std;
const int N=531441,mod=1e9+7;
int n,m,c,q,lim,g[2][N],f[105][4096]; char t[15],s[2][10];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline int comp(CI id,int ret=0)
{
for (RI i=1,j;i+c-1<=m;++i)
{
bool flag=1; for (j=1;j<=c;++j)
if (t[i+j-1]!=s[id][j]) { flag=0; break; }
ret|=(flag<<i-1);
}
return ret;
}
inline void init(CI id,CI nw=1,CI s=0)
{
if (nw>m) return (void)(g[id][s]=comp(id));
t[nw]='W'; init(id,nw+1,s*3);
t[nw]='B'; init(id,nw+1,s*3+1);
t[nw]='X'; init(id,nw+1,s*3+2);
}
inline void FWT(int* f)
{
for (RI i=1,j,k;i<lim;i<<=1) for (j=0;j<lim;j+=(i<<1))
for (k=0;k<i;++k) inc(f[i+j+k],f[j+k]);
}
int main()
{
for (scanf("%d%d%d%d",&n,&m,&c,&q);q;--q)
{
RI i,j; scanf("%s%s",s[0]+1,s[1]+1); lim=1<<m; int tot=quick_pow(3,m);
for (init(0),init(1),memset(f[1],0,sizeof(f[1])),i=0;i<tot;++i)
++f[1][g[0][i]]; for (FWT(f[1]),i=2;i<=n;FWT(f[i]),++i)
for (memset(f[i],0,sizeof(f[i])),j=0;j<tot;++j)
inc(f[i][g[0][j]],f[i-1][(lim-1)^g[1][j]]);
printf("%d\n",(quick_pow(3,n*m)-f[n][lim-1]+mod)%mod);
}
return 0;
}
辣鸡老年选手AFO在即