轮廓线DP(插头DP 裸 经典骨牌)

引言:所谓轮廓线,不是某一行,或者某一列,而是指某一个特定轮廓的状态。


放置骨牌的约定:(保证放置有最优子结构)
假设我们正在放置第i行的骨牌,那么会有下面3种方式:

灰色表示已经有的骨牌,绿色表示新放置的骨牌。
每一种放置方法解释如下,假设当第i行的状态为x,第i-1行的状态为y:

  • 第i行不放置,则前一行必须有放置的骨牌。x对应二进制位为0,y对应二进制位为1。
  • 第i行竖放骨牌,则前一行必须为空。x对应二进制位为1,y对应二进制位为0。
  • 第i行横向骨牌,则前一行必须两个位置均有骨牌,否则会产生空位。x对应二进制位为1,y对应二进制位为1。



    简单的例子:

    Tiling a Grid With Dominoes

    http://acm.hdu.edu.cn/showproblem.php?pid=1992

    限制了棋盘宽度为4,数据不超过int,只有长度22以内满足答案。

    手写状态找规律。

    http://www.cnblogs.com/lzsz1212/archive/2012/05/02/2478839.html


    把这个变成更一般的问题,如果不限制棋盘的宽,


    hihocoder 骨牌问题讨论了窄棋盘情况下(2^min(n,m) 小于200),构造转移矩阵,快速幂的求法。

    http://hihocoder.com/contest/hiho43/problem/1

    转移矩阵构造法,y为i-1行状态,x为i行状态,dfs构造是K^2的复杂度,K=min(n,m)。

    void dfs(int x,int y,int col){
        if(col==K){
            d[y][x]=1;
            return;
        }
        dfs(x<<1,(y<<1)|1,col+1);
        dfs((x<<1)|1,y<<1,col+1);
        if(col+2<=K){
            dfs((x<<2)|3, (y<<2)|3, col+2);
        }
    }

    那么复杂度就是k^3*logn,当k<=7时(2^7 = 128)的计算效率是可以接受的。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    typedef long long LL;
    const int maxn = 1<<7;
    const int mod = 12357;
    int d[maxn][maxn];
    int K,ALL;
    void dfs(int x,int y,int col){
        if(col==K){
            d[y][x]=1;
            return;
        }
        dfs(x<<1,(y<<1)|1,col+1);
        dfs((x<<1)|1,y<<1,col+1);
        if(col+2<=K){
            dfs((x<<2)|3, (y<<2)|3, col+2);
        }
    }
    
    void mul(int a[][maxn],int b[][maxn],int c[][maxn]){
        for(int i=0;i<ALL;i++){
            for(int j=0;j<ALL;j++){
                c[i][j]=0;
            }
        }
        LL t;
        for(int i=0;i<ALL;i++){
            for(int j=0;j<ALL;j++){
                if(!a[i][j])continue;
                for(int k=0;k<ALL;k++){
                    if(b[j][k]){
                        t=(LL)a[i][j]*b[j][k];
                        t+=c[i][k];
                        c[i][k]=t%mod;
                    }
                }
            }
        }
    }
    
    void cpy(int a[][maxn],int b[][maxn]){
        for(int i=0;i<ALL;i++){
            for(int j=0;j<ALL;j++){
                a[i][j]=b[i][j];
            }
        }
    }
    void E(int a[][maxn]){
        for(int i=0;i<ALL;i++){
            a[i][i]=1;
            for(int j=i+1;j<ALL;j++){
                a[i][j]=a[j][i]=0;
            }
        }
    }
    int e[maxn][maxn],tmp[maxn][maxn];
    int main()
    {
    //    freopen("data.in","r",stdin);
        int n;
        scanf("%d%d",&K,&n);
        dfs(0,0,0);
        ALL=1<<K;
        E(e);
        while(n>0){
            if(n&1) {
                mul(e,d,tmp);
                cpy(e,tmp);
            }
            mul(d,d,tmp);
            cpy(d,tmp);
            n>>=1;
        }
        printf("%d\n",e[ALL-1][ALL-1]);
        return 0;
    }
    



    uva11270同此题 ,大白书精讲,但是n*m<101,棋盘可能不是窄棋盘,k^3*logn矩阵运算会超时。

    考虑到n<=10,可以构造2^n*n个转移方程,m轮递求解。

    bfs可以避免访问不能求解的状态。状态st=k4k3k2k1k0,ki表示一个二进制位,有方块就为1,否则为0。


    #include <cstdio>
    #include <iostream>
    #include <cstring>
    using namespace std;
    typedef long long LL;
    typedef LL type;
    typedef pair<int,LL> pil;
    #define mp make_pair
    #define FF first
    #define SS second
    const int maxn = 1<<10;
    int p[2][maxn];
    pil q[2][maxn];
    int tail[2];
    
    void init(int cur){
        memset(p[cur],-1,sizeof p[cur]);
        tail[cur]=0;
    }
    
    int main()
    {
        int n,m,cur,st,pos,nst,hi;
        LL cnt;
        while(scanf("%d%d",&m,&n)!=EOF){
            if(m>n) swap(m,n);
            cur=0;
            st = (1<<m)-1;
            hi = 1<<(m-1);
            init(cur);
            p[0][st]=tail[cur];
            pos = tail[cur]++;
            cnt = 1;
            q[cur][pos]=mp(st,cnt);
            for(int i=0;i<n;i++){
                for(int j=0;j<m;j++,cur^=1){
                    init(cur^1);
                    for(int k=tail[cur]-1;k>=0;k--){
                        st = q[cur][k].FF;
                        cnt = q[cur][k].SS;
    
                        if(st & hi){
                            nst = (st^hi)<<1;
                            if((pos=p[cur^1][nst])==-1){
                                pos = tail[cur^1]++;
                                p[cur^1][nst] = pos;
                                q[cur^1][pos]=mp(nst,cnt);
                            }else{
                                q[cur^1][pos].SS += cnt;
                            }
    
                            if(j && !(st&1)){
                                nst = ((st^hi)<<1)|3;
                                if((pos=p[cur^1][nst])==-1){
                                    pos = tail[cur^1]++;
                                    p[cur^1][nst] = pos;
                                    q[cur^1][pos]=mp(nst,cnt);
                                }else{
                                    q[cur^1][pos].SS += cnt;
                                }
                            }
                        }else{
                            nst = (st<<1)|1;
                            if((pos=p[cur^1][nst])==-1){
                                pos = tail[cur^1]++;
                                p[cur^1][nst] = pos;
                                q[cur^1][pos]=mp(nst,cnt);
                            }else{
                                q[cur^1][pos].SS += cnt;
                            }
                        }
                    }
                }
            }
            st = (1<<m)-1;
            pos = p[cur][st];
            cnt = q[cur][pos].SS;
            printf("%lld\n",cnt);
        }
        return 0;
    }
    

    对于更复杂的插头DP问题后面再讨论


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值