【BZOJ】4572: [Scoi2016]围棋-轮廓线DP

96 篇文章 0 订阅
13 篇文章 0 订阅

传送门:bzoj4572


题解

算可以匹配的比较麻烦,转化成求不能匹配的,取个补集即可。

c6,m12 c ≤ 6 , m ≤ 12 ,数据范围明示轮廓线DP,设 f[k][sta][i][j](0kn,0sta<2mc+1,0i,j<c) f [ k ] [ s t a ] [ i ] [ j ] ( 0 ≤ k ≤ n , 0 ≤ s t a < 2 m − c + 1 , 0 ≤ i , j < c ) 表示当前处理到第 k k 排,sta二进制第 q q 位上0/1表示轮廓线上第q列的位置是否能匹配到模板第一排末尾位置。

容我贴波图,来源:hsfzLZH1-洛谷博客
这里写图片描述

假设 (x,y) ( x , y ) 位于第 x x 行,已经处理到了第y列,轮廓线就是红色标记的一行。

询问时先预处理出kmp转移,第一维可以滚动数组,然后直接DP即可。时间复杂度 O(Q(7c+3nm2mc2)) O ( Q ( 7 c + 3 n m 2 m c 2 ) ) (粗略算了一下)


代码

#include<bits/stdc++.h>
#define RI register
using namespace std;
const int mod=1e9+7,N=(1<<12)+10;

int n,m,c,Q,bin[15],col[10],f[2][N][7][7];
int nxt[2][10],to[2][10][3],tp,mxx,ori,ans;
char s[10];

inline void ad(int &x,int y){x+=y;x-= x>=mod? mod:0;}
inline void dc(int &x,int y){x-=y;x+= x<0? mod:0;}
inline int mul(int x,int y){return 1ll*x*y%mod;}

inline int fp(int x,int y)
{
    int re=1;
    for(;y;y>>=1,x=mul(x,x))
     if(y&1) re=mul(re,x);
    return re; 
}

inline void trans()
{
    scanf("%s",s+1);
    for(RI int i=1;i<=c;++i){
        if(s[i]=='W') col[i]=0;
        else if(s[i]=='B') col[i]=1;
        else col[i]=2;
    }
}

int main(){
    RI int i,j,k,t,s,x,y,a,b,res,kmp;
    bin[0]=1;for(i=1;i<=12;++i) bin[i]=bin[i-1]<<1;
    scanf("%d%d%d%d",&n,&m,&c,&Q);
    mxx=bin[m-c+1];ori=fp(3,n*m);
    for(;Q;--Q){
        tp=0;
        for(i=0;i<2;++i){
            trans();
            for(kmp=0,j=2;j<=c;++j){
                for(;kmp && col[kmp+1]!=col[j];kmp=nxt[i][kmp]);
                kmp+= col[kmp+1]==col[j];
                nxt[i][j]=kmp;
            }
            for(j=0;j<c;++j){
                for(k=0;k<3;++k){
                    for(t=j;t && col[t+1]!=k;t=nxt[i][t]);
                    t+= col[t+1]==k;
                    to[i][j][k]=t;
                }
            }
        }
        memset(f[1],0,sizeof(f[1]));
        f[1][0][0][0]=1;
        for(i=1;i<=n;++i){
            memset(f[tp],0,sizeof(f[tp]));
            for(s=0;s<mxx;++s)
             for(x=0;x<c;++x)
              for(y=0;y<c;++y)
               ad(f[tp][s][0][0],f[tp^1][s][x][y]);
            for(tp^=1,j=1;j<=m;++j,tp^=1){
                memset(f[tp],0,sizeof(f[tp]));
                for(s=0;s<mxx;++s)
                 for(x=0;x<c;++x)
                  for(y=0;y<c;++y) 
                   if(f[tp^1][s][x][y])
                    for(k=0;k<3;++k){
                        a=to[0][x][k],b=to[1][y][k];res=s;
                        if(j>=c && (s&bin[j-c])) res^=bin[j-c];
                        if(a==c) {res^=bin[j-c];a=nxt[0][a];}
                        if(b==c) {if(s&bin[j-c]) continue;b=nxt[1][b];}
                        ad(f[tp][res][a][b],f[tp^1][s][x][y]);
                    }
            }
        } 
        tp^=1;ans=ori;
        for(s=0;s<mxx;++s) 
          for(x=0;x<c;++x)
           for(y=0;y<c;++y)
              dc(ans,f[tp][s][x][y]);
         printf("%d\n",ans);
    }
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值