[SCOI2016]围棋
作者:互联网
题意
做法
首先你得先轮廓线DP(不一定得会插头DP,但这两玩意好像没差)
题解部分照搬:https://www.luogu.com.cn/blog/duyi/solution-p3290,讲的挺好的。
先做补集转化。把“至少有一次匹配”,转化为求“禁止出现匹配”。然后用\(3^{nm}\)减去禁止出现匹配的方案数,就是答案了。
考虑轮廓线DP。
设\(dp[i][j][mask][k][l]\)表示考虑到了第\(i\)行、第\(j\)列这个位置。\(mask\)是一个状压,表示当前轮廓线哪些位置可以与模板的第一行完全匹配。由于\(1…c−1\)这些列显然不可能完全匹配,所以\(mask\)只需要记录\(m-c+1\)个二进制位。\(k\)表示第\(i\)行\(1…j\)位最多能匹配到模板串第一行的哪个位置,\(l\)表示第\(i\)行\(1…j\)位最多能匹配到模板串第二行的哪个位置。
显然,当\(l=c\)且\(mask\)的第一个二进制位(也就是代表了\(i,j\)正上方的位置)为\(1\)时,就会出现一次和模板串的完整匹配,因此这种转移是不合法的。否则,其他情况下我们都可以转移。新的\(k,l\)可以用\(KMP\)求出。新的\(mask\)相比原来的\(mask\)要去掉第一位,然后新加入一位:若\(k=c\)则新加入的位为 \(1\),否则为\(0\)。
DP数组的前两维可以滚动使用,这样优化了空间。
注意,在DP完一整行后,要把所有方案数,累加到\(dp[i][j][mask][0][0]\)上:因为下一行又要重新开始匹配了。
时间复杂度:\(O(nm2^{m-c+1}c^2)\)
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const LL mod=1000000007;
inline LL ksm(LL x,int y)
{
LL ans=1;
while(y)
{
if(y&1)ans=ans*x%mod;
x=x*x%mod;y>>=1;
}
return ans;
}
LL dp[2][4100][10][10];//滚动数组
int kmp[2][10],a[2][10];
char st[2][10];
int n,m,q,c;
inline int get(int k,int x){return (k&(1<<(x-c)))>0;}
inline void change(int &k,int x,int val)
{
if(k&(1<<(x-1)))k^=(1<<(x-1));
k|=(val<<(x-1));
}
int main()
{
scanf("%d%d%d%d",&n,&m,&c,&q);
int limit=(1<<(m-c+1))-1;
for(int t=1;t<=q;t++)
{
for(int i=0;i<=1;i++)
{
scanf("%s",st[i]+1);
kmp[i][0]=-1;a[i][0]=a[i][c+1]=-1;
int k=-1;
for(int j=1;j<=c;j++)
{
a[i][j]=(st[i][j]=='W'?1:(st[i][j]=='B'?2:0));
while(k!=-1 && st[i][k+1]!=st[i][j])k=kmp[i][k];
kmp[i][j]=++k;
}
}
int now=0,pre=1;
memset(dp[now],0,sizeof(dp[now]));
dp[now][0][0][0]=1;//初始化为1
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
now^=1;pre^=1;
memset(dp[now],0,sizeof(dp[now]));
for(int k=0;k<=limit;k++)//之前的状态
{
for(int q=0;q<=c && q<j;q++)//第零行
{
for(int p=0;p<=c && p<j;p++)//第一行
{
if(!dp[pre][k][q][p])continue;
for(int o=0;o<=2;o++)
{
int qq=q,pp=p;
while(qq!=-1 && a[0][qq+1]!=o)qq=kmp[0][qq];
while(pp!=-1 && a[1][pp+1]!=o)pp=kmp[1][pp];
qq++;pp++;
if(pp==c && get(k,j)==1)continue;
int kk=k;
if(j>=c)change(kk,j-c+1,qq==c);
dp[now][kk][qq][pp]=(dp[now][kk][qq][pp]+dp[pre][k][q][p])%mod;
}
}
}
}
}
now^=1;pre^=1;
memset(dp[now],0,sizeof(dp[now]));
for(int k=0;k<=limit;k++)//之前的状态
{
for(int q=0;q<=c;q++)//第零行
{
for(int p=0;p<=c;p++)dp[now][k][0][0]=(dp[now][k][0][0]+dp[pre][k][q][p])%mod;
}
}
}
LL ans=0;
for(int k=0;k<=limit;k++)ans=(ans+dp[now][k][0][0])%mod;
printf("%lld\n",(ksm(3,n*m)-ans+mod)%mod);
}
return 0;
}
小结
这道题目好难,完全不会QAQ
但是感悟也不是没有,其实轮廓线DP有的时候可以优化方格图上某些状压,当状压难以转移,或者可以转移但时间较大时不妨考虑轮廓线DP。
标签:10,DP,int,LL,mask,围棋,SCOI2016,dp 来源: https://www.cnblogs.com/zhangjianjunab/p/13924113.html