插頭dp

引入:賽道

一個n×mn\times m的棋盤,有些格子是正常的,有些格子有障礙。求經過每個正常的格子恰好一次的哈密頓迴路的個數。2n,m122\le n,m\le12

如果直接爆搜,那麼複雜度是O((nm)!)O((nm)!)的,顯然會超時。

因此這題需要dp。

考慮按從上到下,從左到右的順序轉移,則狀態中需要維護一條輪廓線,如圖所示:
Figure 1

考慮把路徑畫出來,那麼輪廓線上有些線段有插頭,有些線段沒有插頭。

那麼可以把輪廓線上每個位置是否有插頭以及插頭的連通性表示出來。

例如圖中狀態可以表示爲(1,2,2,0,1),其中0表示該位置沒有插頭,1和2表示該位置插頭所屬連通塊。

然而這樣狀態數可能很多。可以發現,每個數恰好有兩個,而且插頭構成的這些路徑兩兩不相交。

兩個…兩兩不相交…你想到了什麼?沒錯,括號序列!

把上述狀態轉化爲括號序列,0變成#,一對插頭分別變成()。例如上述狀態可以表示成(()#)

這樣我們就把狀態變成了一個3進制數。

(實際操作中,可以把進制數變成2的次冪,例如4進制,再用哈希表存下,這樣會更快一些)

轉移並不難。

  1. 當格子左、上邊沒有插頭時,

    (###) -> (()#)

  2. 當格子左、上邊有一個插頭時,

    (()#) -> (()#)

    (()#) -> ((#))

  3. 當格子左、上邊都有插頭時,

    1. 當左、上插頭均爲(時,

      #(()) -> ###()

      當左、上插頭均爲)時同理。

    2. 當左、上插頭爲)(時,

      (#)() -> (###)

    3. 當左、上插頭爲()時,

      考慮到連通性,當且僅當轉移到最後一個正常的格子的時候,轉移有效。

開始愉快地貼代碼!

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define R register int
const int N=1594323,L=14;
int n,m,n3=1,e[L],F[N][L],a[L][L],cnt,st[L],pa[L],now;
int t,o,f1,f2,e1,e2;
ll dp[2][N],ans,tdp;
char c[L];
int main(){
    scanf("%d%d",&m,&n);
    for(R i=1;i<=m;i++){
        scanf("%s",c+1);
        for(int j=1;j<=n;j++)if(c[j]=='*')a[i][j]=1;
        else ++cnt;
    }
    for(R i=1;i<=n+1;i++){
        e[i]=n3;
        n3+=n3<<1;
    }
    for(R i=0;i<n3;i++){
        t=i;
        for(R j=1;j<=n+1;j++){
            F[i][j]=t%3;
            t/=3;
        }
    }
    dp[0][0]=1;
    for(R i=1;i<=m;i++){
        for(R j=1;j<=n;j++){
			if(!a[i][j])--cnt;
			now^=1;
			memset(dp[now],0,sizeof(dp[now]));
			e1=e[j];e2=e[j+1];
			for(R k=0;k<n3;k++)if(dp[now^1][k]){
				f1=F[k][j];f2=F[k][j+1];
				tdp=dp[now^1][k];
				if(a[i][j]){
					if(!(f1||f2))dp[now][k]+=tdp;
				}else{
					if(!(f1||f2))dp[now][k+e1+(e2<<1)]+=tdp;
					else if(!(f1&&f2)){
						      dp[now][k           ]+=tdp;
						if(f1)dp[now][k+f1*(e2-e1)]+=tdp;
						else  dp[now][k+f2*(e1-e2)]+=tdp;
					}else{
						if(cnt&&f1==1&&f2==2)continue;
						for(R l=1;l<=n+1;l++)if(F[k][l]){
							if(F[k][l]==1)st[++t]=l;
							else{
								o=st[t--];
								pa[l]=o;
								pa[o]=l;
							}
						}
						if(f1==f2){
							if(f1==1)dp[now][k-(e1+e2   )-e[pa[j+1]]]+=tdp;
							else     dp[now][k-(e1+e2<<1)+e[pa[j  ]]]+=tdp;
						}else{
							if(f1==1)dp[now][k-e1-(e2<<1)]+=tdp;
							else     dp[now][k-e2-(e1<<1)]+=tdp;
						}
					}
				}
			}
		}
		if(i!=m){
			now^=1;
			memset(dp[now],0,sizeof(dp[now]));
			for(int k=0;k<e[n+1];k++)dp[now][k+(k<<1)]+=dp[now^1][k];
		}
    }
    printf("%lld",dp[now][0]);
    return 0;
}

例題:[HNOI2004] 郵遞員

注意要寫高精度(其實__int128也可以)。

#include<bits/stdc++.h>
using namespace std;
#define R register int
const int N=177147,L=12,base=1000000000;
int n,m,n3=1,e[L],F[N][L],st[L],pa[L],now;
int t,o,f1,f2,e1,e2;
__int128 dp[2][N],tdp;
char c[L];
void op(__int128 a){if(a>9)op(a/10);putchar((a%10)^48);}
int main(){
    scanf("%d%d",&n,&m);
    if(n==1||m==1){printf("1");return 0;}
    for(R i=1;i<=n+1;i++){
        e[i]=n3;
        n3+=n3<<1;
    }
    for(R i=0;i<n3;i++){
        t=i;
        for(R j=1;j<=n+1;j++){
            F[i][j]=t%3;
            t/=3;
        }
    }
    dp[0][0]=1;
    for(R k=1;k<n3;k++)dp[0][k]=0;
    for(R i=1;i<=m;i++){
        for(R j=1;j<=n;j++){
            now^=1;
            e1=e[j];e2=e[j+1];
            for(R k=0;k<n3;k++)dp[now][k]=0;
            for(R k=0;k<n3;k++)if(dp[now^1][k]){
                f1=F[k][j];f2=F[k][j+1];
                tdp=dp[now^1][k];
                if(!(f1||f2))dp[now][k+e1+(e2<<1)]+=tdp;
                else if(!(f1&&f2)){
                          dp[now][k           ]+=tdp;
                    if(f1)dp[now][k+f1*(e2-e1)]+=tdp;
                    else  dp[now][k+f2*(e1-e2)]+=tdp;
                }else{
                    if((i!=m||j!=n)&&f1==1&&f2==2)continue;
                    for(R l=1;l<=n+1;l++)if(F[k][l]){
                        if(F[k][l]==1)st[++t]=l;
                        else{
                            o=st[t--];
                            pa[l]=o;
                            pa[o]=l;
                        }
                    }
                    if(f1==f2){
                        if(f1==1)dp[now][k-(e1+e2   )-e[pa[j+1]]]+=tdp;
                        else     dp[now][k-(e1+e2<<1)+e[pa[j  ]]]+=tdp;
                    }else{
                        if(f1==1)dp[now][k-e1-(e2<<1)]+=tdp;
                        else     dp[now][k-e2-(e1<<1)]+=tdp;
                    }
                }
            }
        }
        if(i!=m){
            now^=1;
            for(R k=0;k<n3;k++)dp[now][k]=0;
            for(int k=0;k<e[n+1];k++)dp[now][k+(k<<1)]+=dp[now^1][k];
        }
    }
    dp[now][0]+=dp[now][0];
    op(dp[now][0]);
    return 0;
}

例題:神奇遊樂園

傳送門

注意:

  • 可以把一個格子留空,什麼插頭也沒有。
  • 當左、上插頭爲()時,不一定轉移到最後一個正常的格子的時候轉移纔有效。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define R register int
const int N=2187,L=8,inf=-1000000;
void chk(int &a,int b){if(a<b)a=b;}
int n,m,n3=1,e[L],F[N][L],a[101][L],st[L],pa[L],now,dp[2][N],ans=inf,tdp;
int t,o,f1,f2,e1,e2;
int main(){
    scanf("%d%d",&m,&n);
    for(R i=1;i<=m;i++)for(R j=1;j<=n;j++)scanf("%d",&a[i][j]);
    for(R i=1;i<=n+1;i++){
        e[i]=n3;
        n3+=n3<<1;
    }
    for(R i=0;i<n3;i++){
        t=i;
        for(R j=1;j<=n+1;j++){
            F[i][j]=t%3;
            t/=3;
        }
    }
    dp[0][0]=0;
    for(R k=1;k<n3;k++)dp[0][k]=inf;
    for(R i=1;i<=m;i++){
        for(R j=1;j<=n;j++){
            now^=1;
            for(R k=0;k<n3;k++)dp[now][k]=inf;
            e1=e[j];e2=e[j+1];
            for(R k=0;k<n3;k++)if(dp[now^1][k]!=inf){
                f1=F[k][j];f2=F[k][j+1];
                tdp=dp[now^1][k]+a[i][j];
                if(!(f1||f2)){
                    chk(dp[now][k],tdp-a[i][j]);
                    chk(dp[now][k+e1+(e2<<1)],tdp);
                }
                else if(!(f1&&f2)){
                          chk(dp[now][k           ],tdp);
                    if(f1)chk(dp[now][k+f1*(e2-e1)],tdp);
                    else  chk(dp[now][k+f2*(e1-e2)],tdp);
                }else{
                    if(f1==1&&f2==2){
                        if(k==e1+(e2<<1))chk(ans,tdp);
                        continue;
                    }
                    for(R l=1;l<=n+1;l++)if(F[k][l]){
                        if(F[k][l]==1)st[++t]=l;
                        else{
                            o=st[t--];
                            pa[l]=o;
                            pa[o]=l;
                        }
                    }
                    if(f1==f2){
                        if(f1==1)chk(dp[now][k-(e1+e2   )-e[pa[j+1]]],tdp);
                        else     chk(dp[now][k-(e1+e2<<1)+e[pa[j  ]]],tdp);
                    }else{
                        if(f1==1)chk(dp[now][k-e1-(e2<<1)],tdp);
                        else     chk(dp[now][k-e2-(e1<<1)],tdp);
                    }
                }
            }
        }
        if(i!=m){
            now^=1;
            memset(dp[now],0,sizeof(dp[now]));
            for(R k=0;k<n3;k++)dp[now][k]=inf;
            for(R k=0;k<e[n+1];k++)dp[now][k+(k<<1)]=dp[now^1][k];
        }
    }
    printf("%d",ans);
    return 0;
}

插頭dp練習

P2289 [HNOI2004]郵遞員
P3190 [HNOI2007]神奇遊樂園
P3886 [JLOI2009]神祕的生物
URAL1519
HDU1693
POJ2411
BZOJ 2310: ParkII
P3272,BZOJ 2331[SCOI2011]地板
P4294 [WC2008],BZOJ2595[Wc2008]遊覽計劃
USACO2019 OPEN Compound Escape

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章