1. 程式人生 > >【learning】插頭dp

【learning】插頭dp

位運算 運算 開始 www. fin 負數 情況 span 固定

問題描述

  一種網格棋盤上的回路(路徑也可以)數量統計之類的問題,也可以是求最優值之類的可以考慮dp求解的問題

具體解法

  • 一些必須前置的東西

  首先是一些概念的引入:

  

  1、插頭:既然是插頭dp那肯定要先說說插頭是啥,插頭其實可以理解為每個格子的路經的走向,有以下幾種情況:

技術分享圖片

  2、輪廓線:就是下圖中藍色的那條東西,我們在轉移的時候采用狀態壓縮的方式記錄輪廓線的。。輪廓,然後一個一個格子來轉移

技術分享圖片

  與一般的dp不同,插頭dp是基於輪廓線的dp而不是基於格子的dp,這是很重要的一點

  具體什麽意思呢?就是說我們在記錄和判斷狀態的時候,都是輪廓線對應的插頭的狀態

  清楚了這些東西之後,我們來借助幾道題更加具體地說說這個狀態之類的東西到底是啥

  

Portal-->bzoj1814

?  (哇。。這題為啥是權限題。。)

  簡單說一下題面(其實就是論文題啦),給你一個\(m*n\)的棋盤,有的格子存在障礙不能走,求經過所有非障礙格子的哈密頓回路個數(\(1<=n,m<=12\)

  我們考慮采用逐格遞推的方式來求解,具體什麽意思呢?其實就是cdq論文裏面畫的這個圖,註意觀察深藍色的輪廓線的變化

技術分享圖片

  下面就是重頭戲,狀態的表示

?  由於這個是曼哈頓回路,所以有一個非常優秀的性質:連通路徑不相交

?  思考一下對於一小段輪廓線我們需要記錄什麽信息:插頭的方向和連通性,然後因為這裏有這個不相交的優秀性質,所以我們可以巧妙利用一個括號匹配來解決

?  我們可以將狀態分為三大類(圖中藍色的表示輪廓線,橙黃色是路徑走向):

技術分享圖片

?  後面兩類再說的具體一點就分別是“與右邊的某處輪廓線的插頭配對”和“與左邊的某處輪廓線的插頭配對”

?  我們轉移的時候實際上是由\(L\)\(U\)段的插頭的狀態推到\(D\)\(R\)段的插頭的狀態:

技術分享圖片

?  當考慮一個格子\((i,j)\)的轉移的時候,我們需要關註的是這個格子左邊界的輪廓線(提取狀態出來之後是第\(j\)段輪廓線)對應的插頭和上邊界輪廓線(第\(j+1\)段輪廓線)對應的插頭,那麽接下來就是愉快的大力分類討論時間:

1、技術分享圖片

這個格子是障礙,那麽如果說上面和左邊都沒有路徑過來(沒有插頭的話,說明路徑繞過了這個障礙,狀態合法,如果有則不合法,不轉移

2、技術分享圖片

這個格子不是障礙並且上、左都沒有路徑過來,那麽我們可以考慮新加一個“轉角”(如上圖),那也就是\(j\)段插頭狀態變成\(1\)\(j+1\)段插頭狀態變成\(2\)

3、技術分享圖片

這個格子不是障礙並且左邊或上面有一個沒有路徑過來,那就直接延伸原來的路徑就好了

4、技術分享圖片

這個格子不是障礙並且左邊的是一個通向左邊的插頭,上面的是一個通向右邊的插頭,那麽顯然中間必須要連起來了,所以新的狀態中\(j\)段和\(j+1\)段都是沒有插頭的

5、技術分享圖片

這個格子不是障礙並且左插頭通向右邊,上插頭通向左邊,那麽這個時候就應該統計答案了(因為已經圍成了一個回路),判斷一下輪廓線上是否只有這兩個地方是有插頭的,如果是的話說明只有一條回路,可以作為一個答案

6、技術分享圖片

這個格子不是障礙並且左插頭和上插頭同向,那麽這個時候也是應該在\((i,j)\)這個格子連起來,然後這樣一來,左插頭或者上插頭原先對應的那個匹配的插頭狀態需要調整(因為一旦連通起來了就不可能有兩對\(1,2\)狀態的插頭,而是應該只有一對),以第一幅圖為例,當兩個插頭都是\(1\)插頭的時候,上插頭原來對應的\(2\)插頭應該要變為\(1\)插頭,因為與之配對的插頭到右邊去了;然後如果在第二幅圖裏面的話,就應該是左插頭對應的\(1\)插頭變為\(2\)插頭

  

  就此我們十分愉快地討論完了,然後這題就這樣做完了

?  這裏有一個小trick就是,雖然說插頭狀態只有三個,但是其實為了快,我們完全可以用一個四進制數來存(這樣就可以直接位運算了嘛),大力哈希一下就好了

  然後對於情況6中的尋找與之配對的插頭這個,我們可以直接用括號匹配的方式來尋找

  

?  其實想明白了還是挺好寫的就是調試的時候會有點惡心qwq

?   

  代碼大概長這個樣子(不過說實話感覺插頭dp還是自己寫一遍比較清楚)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define behind 1
#define front -1
using namespace std;
const int N=12+3,HS=2333,TOT=1e6+10;
int n,m,edi,edj;
struct Hash{/*{{{*/
    int st[TOT],hd[HS],nxt[TOT];
    ll sum[TOT];
    int tot;
    void add(int x,int id,ll data){
        st[++tot]=id; nxt[tot]=hd[x]; hd[x]=tot; sum[tot]=data;
    }
    void insert(int id,ll data){
        int x=id%HS;
        for (int i=hd[x];i;i=nxt[i])
            if (st[i]==id){sum[i]+=data;return;}
        add(x,id,data);
    }
    void clear(){
        for (int i=0;i<HS;++i) hd[i]=0;
        tot=0;
    }
    void TakeOut(int *rec1,ll *rec2,int &len){
        len=0;
        for (int i=0;i<HS;++i){
            for (int j=hd[i];j;j=nxt[j]){
                rec1[++len]=st[j],rec2[len]=sum[j];
            }
        }
    }
}h[2];/*}}}*/
int mp[N][N],pw[N],rec_st[TOT];
ll rec_sum[TOT];
int now,pre,len;
ll ans;
void init();
void solve();
void dp(int i,int j);
int query(int st,int x){st>>=((x-1)<<1); return st-((st>>2)<<2);}//4 jinzhi
void add_st(int j,int st,ll val){h[now].insert(j==m?((st-query(st,m+1)*pw[m])<<2):st,val);}
void change(int &st,int x,int val){st+=(val-query(st,x))*pw[x-1];}
int find(int st,int x,int mark);

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    char ch;
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j){
            scanf("%c",&ch);
            mp[i][j]=ch=='.';
            if (ch=='.') 
                edi=i,edj=j;
        }
        scanf("\n");
    }
    init();
    solve();
    printf("%lld\n",ans);
}

void init(){
    pw[0]=1;
    for (int i=1;i<=12;++i) pw[i]=pw[i-1]<<2;
    ans=0;
}

void solve(){
    now=1,pre=0;
    h[pre].insert(0,1);
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j){
            h[now].clear();
            if (i==4&&j==1)
                int debug=1;
            dp(i,j);
            swap(now,pre);
        }
    }
}

void dp(int i,int j){
    h[pre].TakeOut(rec_st,rec_sum,len);
    int st,plugl,plugu,nw,pos;
    ll val;
    while (len){
        st=rec_st[len]; val=rec_sum[len]; --len;
        plugl=query(st,j);
        plugu=query(st,j+1);
        if (!mp[i][j]){//not ok
            if (plugl==0&&plugu==0)
                add_st(j,st,val);
            continue;
        }
        if (plugl==0&&plugu==0){
            if (i<n&&j<m){
                nw=st;
                change(nw,j,1);
                change(nw,j+1,2);
                add_st(j,nw,val);
            }
        }
        else if (plugl==0||plugu==0){
            if (i<n){
                nw=st;
                change(nw,j,plugu+plugl);
                change(nw,j+1,0);
                add_st(j,nw,val);
            }
            if (j<m){
                nw=st;
                change(nw,j,0);
                change(nw,j+1,plugu+plugl);
                add_st(j,nw,val);
            }
        }
        else if (plugl==2&&plugu==1){
            nw=st;
            change(nw,j,0);
            change(nw,j+1,0);
            add_st(j,nw,val);
        }
        else if (plugl==1&&plugu==2){
            if (i==edi&&j==edj){
                int check=true;
                for (int tmp=j+2;tmp<=m+1&&check;++tmp)
                    if (query(st,tmp)) check=false;
                if (check) ans+=val;//!!!check!!!
            }
        }
        else if (plugl==1&&plugu==1){
            pos=find(st,j+1,behind);
            nw=st;
            change(nw,j,0);
            change(nw,j+1,0);
            change(nw,pos,1);
            add_st(j,nw,val);
        }
        else if (plugl==2&&plugu==2){
            pos=find(st,j,front);
            nw=st;
            change(nw,j,0);
            change(nw,j+1,0);
            change(nw,pos,2);
            add_st(j,nw,val);
        }
    }
}


int find(int st,int x,int step){
    int top=1,mark=query(st,x),tmp;
    x+=step;
    for (;top;x+=step){
        tmp=query(st,x);
        if (!tmp) continue;
        if (tmp==mark) ++top;
        else --top;
    }
    return x-step;
}

  

Portal-->bzoj3125

  有了上面那題的鋪墊,這題做起來就。。其實差不多嘛

  不同的是,有些塊的方向是固定的,那麽這個只要在上面分的幾大類中再單獨判斷一下就好了

  下面附上dp部分的代碼

void dp(int i,int j){
    h[pre].TakeOut(rec_st,rec_sum,len);
    int st,plugl,plugu,nw,pos;
    ll val;
    while (len){
        st=rec_st[len]; val=rec_sum[len]; --len;
        plugl=query(st,j);
        plugu=query(st,j+1);
        if (mp[i][j]=='#'){//not ok
            if (plugl==0&&plugu==0)
                add_st(j,st,val);
            continue;
        }
        if (plugl==0&&plugu==0&&mp[i][j]=='.'){
            if (i<n&&j<m){
                nw=st;
                change(nw,j,1);
                change(nw,j+1,2);
                add_st(j,nw,val);
            }
        }
        else if (plugl==0||plugu==0){
            if (i<n){
                nw=st;
                if (mp[i][j]=='.'){
                    change(nw,j,plugu+plugl);
                    change(nw,j+1,0);
                    add_st(j,nw,val);
                }
                else if (mp[i][j]=='|'&&plugu){
                    change(nw,j,plugu);
                    change(nw,j+1,0);
                    add_st(j,nw,val);
                }
            }
            if (j<m){
                nw=st;
                if (mp[i][j]=='.'){
                    change(nw,j,0);
                    change(nw,j+1,plugu+plugl);
                    add_st(j,nw,val);
                }
                else if (mp[i][j]=='-'&&plugl){
                    change(nw,j,0);
                    change(nw,j+1,plugl);
                    add_st(j,nw,val);
                }
            }
        }
        else if (plugl==2&&plugu==1&&mp[i][j]=='.'){
            nw=st;
            change(nw,j,0);
            change(nw,j+1,0);
            add_st(j,nw,val);
        }
        else if (plugl==1&&plugu==2&&mp[i][j]=='.'){
            if (i==edi&&j==edj){
                int check=true;
                for (int tmp=j+2;tmp<=m+1&&check;++tmp)
                    if (query(st,tmp)) check=false;
                if (check) ans+=val;//!!!check!!!
            }
        }
        else if (plugl==1&&plugu==1&&mp[i][j]=='.'){
            pos=find(st,j+1,behind);
            nw=st;
            change(nw,j,0);
            change(nw,j+1,0);
            change(nw,pos,1);
            add_st(j,nw,val);
        }
        else if (plugl==2&&plugu==2&&mp[i][j]=='.'){
            pos=find(st,j,front);
            nw=st;
            change(nw,j,0);
            change(nw,j+1,0);
            change(nw,pos,2);
            add_st(j,nw,val);
        }
    }
}

  

Portal -->bzoj1187

  這題的話,不同的只是多了一個滿意度,那麽直接把計算方案數改成滿意度取max就好了

?  長得都差不多代碼就。。不貼了吧

  

Portal -->bzoj2310

?  最後這題,就是重頭戲了(然而還是權限題。。)

?  說一下題面:給你一個\(m*n\)的矩陣,每個格子有一個權值\(v(i,j)\),權值可能是負數,要求找一條路徑,使得每個點最多經過一次,並且經過的點權值和最大

  

  好的,我們發現這題不再是求回路了,那麽怎麽辦?

?  其實我們只需要多一個插頭狀態就可以解決這個問題了,我們多加一個插頭狀態\(4\),表示這是一個獨立插頭,具體什麽意思呢?就是因為現在我們求的不是回路了,所以並不是所有的插頭都兩兩匹配,有的插頭是作為整條路徑的一端,那麽我們將這種插頭稱為獨立插頭

  接下來依舊是原來的處理方式和轉移方式,我們繼續開始愉快分類討論,有了前面的示例,這裏就不再畫圖了,為了讓描述變得更加簡潔,下面我們用\(plugu\)表示上邊界插頭的狀態,\(plugl\)表示左邊界插頭的狀態,轉移後對應\(plugr\)表示右邊界插頭的狀態,\(plugd\)表示左邊界插頭的狀態

1、\(plugl=0,plugu==0\)

?  當前的格子可能成為路徑的一端,也就是轉移後\(plugr=4\)或者\(plugd=4\),另一個就為\(0\)

2、\(plugl=0\)\(plugr=0\)

?  如果說沒有超出邊界的話,顯然可以繼續延伸(也就是例題一種的情況6)

  此外如果說非零的那個插頭是獨立插頭,那麽我們判斷是否只有一條路徑然後就可以更新答案了

  如果說非零的那個插頭不是獨立插頭而是\(1\)或者\(2\)插頭的話,說明它可以成為路的一端作為一個獨立插頭,對應的這個插頭原來對應的位置也要變成獨立插頭

3、\(plugl=3,plugu=3\)

?  這種情況下也是判斷一下是否只有一條路徑,然後就可以更新答案了

4、\(或plugl=1或2,plugu=3\)

?  連起來之後,\(plugl\)原來對應的插頭要變成獨立插頭作為路徑的另一端存在

5、\(或plugl=3,plugu=1或2\)

  跟情況4類似

6、其他的\(plugl<=2,plugu<=2\)的情況,都與例題一中的討論情況一致

  

?  同樣也是。。想明白了之後很好寫但是!調試起來很惡心qwq

  同樣是建議自己寫qwq

?  不過同樣還是附上參考代碼qwq

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define front -1
#define behind 1
using namespace std;
const int N=10,HS=2333,TOT=1e6+10;
const ll inf=1LL<<60;
struct Hash{/*{{{*/
    int nxt[TOT],id[TOT],st[TOT],hd[TOT];
    ll val[TOT];
    int tot;
    void add(int x,int id,ll data){
        st[++tot]=id; nxt[tot]=hd[x]; hd[x]=tot; val[tot]=data;
    }
    void insert(int id,ll data){
        if (id==1)
            int debug=1;
        int x=id%HS;
        for (int i=hd[x];i;i=nxt[i])
            if (st[i]==id){val[i]=max(val[i],data);return;}
        add(x,id,data);
    }
    void clear(){for (int i=0;i<HS;++i) hd[i]=0;tot=0;}
    void TakeOut(int *rec1,ll *rec2,int &len){
        len=0;
        for (int i=0;i<HS;++i)
            for (int j=hd[i];j;j=nxt[j])
                rec1[++len]=st[j],rec2[len]=val[j];
    }
}h[2];/*}}}*/
int rec_st[TOT],pw[N];
ll rec_val[TOT],v[110][N];
ll ans;
int n,m,now,pre,len;
void prework();
int query(int st,int x){st>>=((x-1)<<1);return st-((st>>2)<<2);}
void change(int &st,int x,int val){st+=(val-query(st,x))*pw[x-1];}
void add_st(int st,int j,ll val){h[now].insert(j==m?(st-query(st,m+1)*pw[m])<<2:st,val);}
int find(int st,int x,int step);
void solve();
void dp(int i,int j);
int calc(int st);

int main(){
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
            scanf("%lld",&v[i][j]);
    prework();
    solve();
    printf("%lld\n",ans);
}

void prework(){
    pw[0]=1;
    for (int i=1;i<N;++i) pw[i]=pw[i-1]<<2;
    ans=-inf;
}

int find(int st,int x,int step){
    int cnt=1,mark=query(st,x),tmp;
    for (x+=step;cnt;x+=step){
        tmp=query(st,x);
        if (!tmp||tmp==3) continue;
        if (tmp==mark) ++cnt;
        else --cnt;
    }
    return x-step;
}

void solve(){
    now=1,pre=0;
    h[pre].insert(0,0);
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j){
            h[now].clear();
            dp(i,j);
            swap(now,pre);
        }
}

int calc(int st){
    int ret=0;
    for (;st;st>>=2)
        ret+=(st-((st>>2)<<2))==3;
    return ret;
}

void dp(int i,int j){
    int st,plugl,plugu,pos,which,dir,nw;
    ll val;
    h[pre].TakeOut(rec_st,rec_val,len);
    while (len){
        st=rec_st[len]; val=rec_val[len]; --len;
        plugl=query(st,j);
        plugu=query(st,j+1);

        nw=st;
        if (plugl==0&&plugu==0){//(0,0)
            add_st(st,j,val);
            if (i<n&&j<m)
                change(st,j,1),change(st,j+1,2),add_st(st,j,val+v[i][j]);

            if (calc(nw)<=1){
                if (i<n)
                    change(nw,j,3),change(nw,j+1,0),add_st(nw,j,val+v[i][j]);
                if (j<m)
                    change(nw,j,0),change(nw,j+1,3),add_st(nw,j,val+v[i][j]);
            }
        }
        else if (plugl==0||plugu==0){
            if (i<n)
                change(nw,j,plugu+plugl),change(nw,j+1,0),add_st(nw,j,val+v[i][j]);
            if (j<m)
                change(nw,j,0),change(nw,j+1,plugu+plugl),add_st(nw,j,val+v[i][j]);

            which=plugl?j:j+1,dir=plugl+plugu==1?behind:front;
            if (plugu+plugl==3){//(0,3)  (3,0)
                int flag=true;
                for (int tmp=1;tmp<=m+1&&flag;++tmp)
                    if (tmp!=which&&query(st,tmp)) flag=false;
                if (flag)
                    ans=max(ans,val+v[i][j]);
            }
            else{// (0,1/2)  (1/2,0)
                pos=find(st,which,dir);
                change(nw,pos,3); change(nw,j,0); change(nw,j+1,0);
                add_st(nw,j,val+v[i][j]);
            }
        }
        else if (plugl==plugu&&plugu<=2){//(1,1)   (2,2)
            which=plugl==1?j+1:j; dir=plugl==1?behind:front;
            pos=find(st,which,dir);
            change(nw,pos,plugl); change(nw,j,0); change(nw,j+1,0);
            add_st(nw,j,val+v[i][j]);
        }
        else if ((plugl==3&&plugu==3)||(plugl==1&&plugu==2)){//(3,3)  (1,2)
            int flag=true;
            for (int tmp=1;tmp<=m+1&&flag;++tmp)
                if (tmp!=j&&tmp!=j+1&&query(st,tmp)) flag=false;
            if (flag)
                ans=max(ans,val+v[i][j]);
        }
        else if (plugl==2&&plugu==1){//(2,1)
            change(nw,j,0); change(nw,j+1,0);
            add_st(nw,j,val+v[i][j]);
        }
        else if (plugl<=2&&plugu==3){//(1/2,3)
            dir=plugl==1?behind:front;
            pos=find(st,j,dir);
            change(nw,pos,3); change(nw,j,0); change(nw,j+1,0);
            add_st(nw,j,val+v[i][j]);
        }
        else if (plugl==3&&plugu<=2){//(3,1/2)
            dir=plugu==1?behind:front;
            pos=find(st,j+1,dir);
            change(nw,pos,3); change(nw,j,0); change(nw,j+1,0);
            add_st(nw,j,val+v[i][j]);
        }
    }
}

  
?  

?  最後附上參考資料:Portal -->cdq論文

【learning】插頭dp