1. 程式人生 > >輪廓線DP(插頭DP 裸 經典骨牌)

輪廓線DP(插頭DP 裸 經典骨牌)

引言:所謂輪廓線,不是某一行,或者某一列,而是指某一個特定輪廓的狀態。

放置骨牌的約定:(保證放置有最優子結構)
假設我們正在放置第i行的骨牌,那麼會有下面3種方式:

灰色表示已經有的骨牌,綠色表示新放置的骨牌。
每一種放置方法解釋如下,假設當第i行的狀態為x,第i-1行的狀態為y:

第i行不放置,則前一行必須有放置的骨牌。x對應二進位制位為0,y對應二進位制位為1。第i行豎放骨牌,則前一行必須為空。x對應二進位制位為1,y對應二進位制位為0。第i行橫向骨牌,則前一行必須兩個位置均有骨牌,否則會產生空位。x對應二進位制位為1,y對應二進位制位為1。

簡單的例子:

限制了棋盤寬度為4,資料不超過int,只有長度22以內滿足答案。

手寫狀態找規律。

把這個變成更一般的問題,如果不限制棋盤的寬,

hihocoder 骨牌問題討論了窄棋盤情況下(2^min(n,m) 小於200),構造轉移矩陣,快速冪的求法。

轉移矩陣構造法,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問題後面再討論