Hnoi2007-Day1有一道題目 Park:給你一個 m * n 的矩陣,每一個矩陣內有個
權值V(i,j) (可能爲負數),要求找一條迴路,使得每一個點最多通過一次,而且通過
的點權值之和最大,想必你們印象深入吧.
無聊的小 C 同窗把這個問題稍微改了一下:要求找一條路徑,使得每一個點
最多通過一次,而且點權值之和最大,若是你跟小 C 同樣無聊,就麻煩作一下
這個題目吧.
轉載請註明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.htmlhtml
最近搞了一下插頭DP的基礎知識……這真的是一種很鍛鍊人的題型……node
每一道題的狀態都不同,而且有很多的分類討論,讓插頭DP十分鍛鍊思惟的全面性和嚴謹性。數組
下面咱們一塊兒來學習插頭DP的內容吧!app
插頭DP主要用來處理一系列基於連通性狀態壓縮的動態規劃問題,處理的具體問題有不少種,而且通常數據規模較小。ide
因爲棋盤有很特殊的結構,使得它能夠與「連通性」有很強的聯繫,所以插頭DP最多見的應用要數在棋盤模型上的應用了。函數
下面咱們給出一道很簡單的例題,而且由這道簡單的例題構建出插頭DP的基本解題思路,在狀態確立,狀態轉移以及程序實現幾個方面進行一一介紹.post
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)學習
首先,咱們要了解插頭DP中最重要的關鍵詞:「插頭」優化
---插頭ui
在插頭DP中,插頭表示一種聯通的狀態,以棋盤爲例,一個格子有一個向某方向的插頭,就意味着這個格子在這個方向能夠與外面相連(與插頭那邊的格子聯通)。
值得注意的一點是,插頭不是表示將要去某處的虛擬狀態,而是表示已經到達某處的現實狀態。
也就是說,若是有一個插頭指向某個格子,那麼這個格子已經和插頭來源聯通了,咱們接下來要考慮的是從這個插頭往哪裏走。
這是很重要的一點理解,下面討論的狀態轉移都是在此基礎上展開的,請務必注意。
咱們已經有了插頭,天然要利用插頭來狀態轉移。通常來講,咱們從上往下,從左往右逐行逐格遞推。
---逐格遞推
咱們考慮第i行的某一個格子:走向它的方案,可能由上一行的下插頭轉移而來,也多是本行的右插頭轉移而來。
所以咱們須要記錄這些地方有沒有插頭,也就是利用狀壓的思想。咱們記錄的這個「有沒有插頭」的東西,就被咱們稱爲輪廓線。字面意思,輪廓線就是記錄了棋盤這一行與上一行交界的輪廓中插頭的狀況。輪廓線上方是已經決策完的格子,下方是未決策的。顯然,對於本題的輪廓線,與它直接相連的格子有m個,插頭有m+1個,我我的的習慣是給插頭編號0~m。
·上圖就是輪廓線的一種可能狀況。
因爲數據範圍比較小,輪廓線的插頭狀態咱們通常能夠利用X進制壓位來表示。
對於本題來講,題目的限制條件比較少,能夠走多個迴路,而不是像某些題同樣只能走一個迴路(走一個迴路的時候要維護插頭間的連通性,咱們下文再討論),所以咱們直接記錄,只要用二進制來表示某一位置有沒有插頭便可:設0表示沒有插頭,1表示有插頭。(大概這是最簡單的一種插頭類型了......)
---行間轉移
咱們先考慮兩行之間的轉移:顯然,第i行的下插頭決定了第i+1行的格子有沒有上插頭,所以咱們應該把這個信息傳遞到下一行。
在轉移的時候,當前行插頭0到插頭m-1可能會給下一行帶來貢獻,而第m個插頭必定爲0(結合定義,想一下爲何)。
容易發現,當前行的0~m-1號插頭會變成下一行初始的1~m號插頭,所以咱們能夠直接利用位運算進行轉移。
對於本題,只須要將上一行的某個狀態左移一位(<<1,即*2)便可
行間的轉移仍是比較簡單的,具體代碼實現的話,下面是一種能夠參考的方式
(這是我剛學插頭DP時候用的一種比較蠢的打法,使用狀態數組f[i][j][k]表示決策到第i行第j列,插頭狀態爲k的方案數,後面使用Hash表的時候咱們還有其餘方式)
1 if(i<n)//bin[i]表示2的i次方 2 for(int j=0;j<bin[m];j++) 3 f[i+1][0][j<<1]=f[i][m][j];
下面咱們考慮具體的逐格轉移,這也是插頭DP的核心模塊所在。
對於本題來講,當咱們決策到某個格子(x,y)時,假如它不是障礙格子,可能會出現以下三種狀況:
狀況1,這個格子沒有上插頭,也沒有左插頭,那麼因爲咱們要遍歷整張圖,因此咱們要新建插頭,把這個格子與其餘格子連起來,相應的,咱們要把原來輪廓線對應位置的插頭改成1.
狀況2,這個位置有上插頭,也有左插頭。因爲咱們不要求只有一條迴路,所以迴路能夠在這裏結束。咱們直接更新答案便可。
狀況3,只有一個插頭。那麼這個插頭能夠向其餘方向走:向下和向右都可以。因此咱們修改一下輪廓線並更新對應狀態的答案便可。
值得注意的是,若是一個格子是障礙格,那麼當且僅當沒有插頭連向它時,這纔是一個合法狀態。由於根據咱們剛纔插頭的定義:
「值得注意的一點是,插頭不是表示將要去某處的虛擬狀態,而是表示已經到達某處的現實狀態。
也就是說,若是有一個插頭指向某個格子,那麼這個格子已經和插頭來源聯通了,咱們接下來要考慮的是從這個插頭往哪裏走。」
因此,對應障礙格既不能連入插頭,也不能連出插頭。這一點須要特別注意。
本題的分類討論仍是相對簡單的,在處理完上面的內容後咱們只須要按照上面的思路代碼實現便可。
(代碼裏狀態轉移頗有意思……)
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 typedef long long LL; 5 int n,m,bin[20],mp[13][13]; 6 LL f[13][13][(1<<12)+10]; 7 inline void Execution(int x,int y) 8 { 9 int plug1=bin[y-1],plug2=bin[y]; 10 for(int j=0;j<bin[m+1];j++) 11 if(mp[x][y]) 12 { 13 f[x][y][j]+=f[x][y-1][j^plug1^plug2]; 14 if( (( j>>(y-1) )&1)== ((j>>(y) )&1) )continue; 15 f[x][y][j]+=f[x][y-1][j]; 16 } 17 else 18 if(!(j&plug1)&&!(j&plug2))f[x][y][j]=f[x][y-1][j]; 19 else f[x][y][j]=0; 20 } 21 int main() 22 { 23 int t;scanf("%d",&t); 24 bin[0]=1;for(int i=1;i<=15;i++)bin[i]=bin[i-1]<<1; 25 for(int u=1;u<=t;u++) 26 { 27 scanf("%d%d",&n,&m); 28 for(int i=1;i<=n;i++) 29 for(int j=1;j<=m;j++) 30 scanf("%d",&mp[i][j]); 31 memset(f,0,sizeof(f));f[1][0][0]=1; 32 for(int i=1;i<=n;i++) 33 { 34 for(int j=1;j<=m;j++)Execution(i,j); 35 if(i!=n)for(int j=0;j<bin[m];j++) 36 f[i+1][0][j<<1]=f[i][m][j]; 37 } 38 printf("Case %d: There are %lld ways to eat the trees.\n",u,f[n][m][0]); 39 } 40 }
經過剛纔這道題,你應該已經對插頭DP是什麼,以及插頭DP的基本概念與思想有了基本的瞭解。
那麼下面,咱們經過下一道題來強化分類討論能力,以及學習對連通性的限制方法。
時間限制:10 s 內存限制:162 MB
Smith在P市的郵政局工做,他天天的工做是從郵局出發,到本身所管轄的全部郵筒取信件,而後帶回郵局。
他所管轄的郵筒很是巧地排成了一個m*n的點陣(點陣中的間距都是相等的)。左上角的郵筒剛好在郵局的門口。
Smith是一個很是標新立異的人,他但願天天都能走不一樣的路線,可是同時,他又不但願路線的長度增長,他想知道他有多少條不一樣的路線可走。
你的程序須要根據給定的輸入,給出符合題意的輸出:
l 輸入包括點陣的m和n的值;
l 你須要根據給出的輸入,計算出Smith可選的不一樣路線的總條數;
輸入文件postman.in只有一行。包括兩個整數m, n(1 <= m <= 10, 1 <= n <= 20),表示了Smith管轄內的郵筒排成的點陣。
輸出文件只有一行,只有一個整數,表示Smith可選的不一樣路線的條數。
2 2 說明:該輸入表示,Smith管轄了2*2的一個郵筒點陣。
2
有了上一題的經驗,不難看出,本題依然是一個在棋盤模型上解決的簡單迴路問題(簡單迴路是指起點和終點相同的簡單路徑)。
而咱們要求的是能一遍遍歷整個棋盤的簡單迴路個數。
但是,若是直接搬用上一題的作法,你會發現一些問題:
好比對於上圖的狀況,在上一題中這是一個合法解,但在本題中不是。那麼咱們就應該思考上題插頭定義的片面性在哪裏,並想出新的插頭定義。
容易觀察到,若是每一個格子都在迴路中的話,最後全部的格子應該都經過插頭鏈接成了一個連通塊。
所以咱們還須要記錄每行格子的連通狀況.這時咱們就要引入一種新的方法:最小表示法。這是一種用來標記連通性的方法。
具體的過程是:第一個非障礙格子以及與它連通的全部格子標記爲1,而後再找第一個未標記的非障礙格子以及與它連通的格子標記爲2,……重複這個過程,直到全部的格子都標記完畢.好比連通訊息((1,2,5),(3,6),(4)),就能夠表示爲{1,1,2,3,1,2}
可是,在實際的代碼實現中,這樣的最小表示法有些冗餘:若是某個格子沒有下插頭,那麼它就不會對下一行的格子產生影響,這個狀態就是多餘的。
所以,咱們轉換優化的角度,用最小表示法來表示插頭的聯通性:若是這個插頭存在,那麼就標記這個插頭對應的格子的連通標號,若是這個插頭不存在,那麼標記爲0..
在這樣優化後,不只狀態表示更加簡單,並且狀態總數將會大大減小.
接下來,咱們用改進過的最小表示法,繼續思考上面的問題:如何定義新的插頭狀態?
若是每一個格子都在迴路中的話,咱們還能夠獲得,每一個格子應該剛好有且僅有2個插頭。
咱們來看下面幾張圖片:
相信細心的你可以發現,輪廓線上方的路徑是由若干條互不相交的路徑構成的(這是確定的,簡單反證:若是最終相交就構不成迴路了)。
更有趣的是,每條路徑的兩個端點剛好對應了輪廓線上的兩個插頭。
咱們又知道,一條路徑應該對應着一個連通塊,所以這兩個插頭同屬一個連通塊,而且不與其餘的連通塊聯通
而且,咱們在狀態轉移的時候也不會改變這種性質:
上文的狀況1對應着新增一條路徑,插頭爲2;
狀況2意味着把2條路徑合爲一條,聯通塊變爲1個,插頭仍是2個;
狀況3只有一個插頭壓根不會改變插頭數量。
那麼如今咱們知道了,簡單迴路問題必定知足任什麼時候候輪廓線上每個連通份量剛好有2個插頭。
互不相交……兩個插頭……展開你的聯想,你能想到什麼?
沒錯,這正是括號匹配!咱們能夠按照與括號匹配類似的方式,將輪廓線上每一條路徑上中左邊那個插頭標記爲左括號插頭,右邊那個插頭標記爲右括號插頭。
因爲插頭之間不會交叉,那麼左括號插頭必定能夠與右括號插頭一一對應。
這樣咱們就能夠解決上面的聯通性問題:咱們可使用一種新的定義方式:3進製表示——0表示無插頭,1表示左括號插頭,2表示右括號插頭,記錄下全部的輪廓線信息。
可是,值得注意的是,X進制的解碼轉碼是較慢並且較麻煩的。
在空間容許的狀況下,建議使用2k進制,而且加上Hash表去重。這樣不只能夠減小狀態,因爲仍然可使用二進制位運算,運算速度相比之下也增長了很多。
下面,咱們利用剛纔新的插頭定義方式來考慮本題的狀態轉移問題。
依然設當前轉移到格子(x,y),設y-1號插頭狀態爲p1,y號插頭狀態爲p2。
狀況1:p1==0&&p2==0.
這種狀態和上一題的狀況1是相似的,咱們只須要新建一個新路徑便可:下插頭設爲左括號插頭,右插頭設爲右括號插頭
狀況2:p1==0&&p2!=0.
這種狀態和上一題的狀態3相似,咱們依然能夠選擇「直走」和「轉彎」兩種策略
狀況3:p1!=0&&p2==0.
這種狀態和狀況2相似,再也不贅述。
狀況4:p1==1&&p2==1.
這種狀態把2個左括號插頭相連,那麼咱們須要將右邊那個左括號插頭(p2)對應的右括號插頭q2修改爲左括號插頭。
狀況5:p1==1&&p2==2.
因爲路徑兩兩不相交,因此這種狀況只能是本身和本身撞在了一塊兒,即造成了迴路。
因爲只能有一條迴路,所以只有在x==n&&y==m時,這種狀態纔是合法的,咱們能夠用它更新答案。
狀況6:p1==2&&p2==1.
這種狀態至關於把2條路徑相連,並無更改其餘的插頭
狀況7:p1==2&&p2==2.
這種狀態與狀況4類似,這種狀態把2個右括號插頭相連,那麼咱們須要將左邊那個右括號插頭(p1)對應的左括號插頭q1修改爲右括號插頭。
接下來咱們只要代碼實現上述過程便可。
但咱們依然有一個很大的優化點:Hash表的使用。Hash表能夠經過去重以及排除無用狀態極大的加速插頭DP的速度。
Hash表的打法不惟一,下面僅介紹我學習的打法(感謝stdafx學長)
與Hash表相關的主要內容有:
1.mod變量,爲Hash表的大小和模數
2.size變量,存儲Hash表大小;
3.hash數組,存儲某個餘數對應的編號
4.key數組,存儲狀態
5.val數組,存某個狀態對應的方案數
在給出一個新狀態時,咱們在已有Hash表內搜索是否存在這一狀態,若是有,那就修改這個狀態對應的val值;若是沒有,那就給他新建一個編號
具體的代碼實現大概長這樣:
1 struct node{int state,next;}; 2 struct Hash_map 3 { 4 int val[MOD],adj[MOD],e;node s[MOD]; 5 inline void intn() 6 { 7 memset(val,0x7f,sizeof(val)),e=0, 8 memset(s,0,sizeof(s)),memset(adj,0,sizeof(adj)); 9 } 10 inline int &operator [] (const int &State) 11 { 12 int pos=State%MOD,i; 13 for(i=adj[pos];i&&s[i].state!=State;i=s[i].next); 14 if(!i)s[++e].state=State,s[e].next=adj[pos],adj[pos]=i=e; 15 return val[i]; 16 } 17 }f[2];
有了Hash表,咱們再來考慮狀態轉移時的幾個小細節:
咱們狀態轉移的主要工做通常有三個:
1.查詢某個插頭對應的類型(對應下文Find)
2.查找與某個插頭匹配的對應插頭(對應下文Link)
3.修改狀態中某個插頭的類型(對應下文Set)
因爲這三個操做很經常使用,因此我把他們寫成了函數,方便調用。這三個操做的代碼見下:
1 inline int Find(int State,int id){return (State>>((id-1)<<1))&3;} 2 inline void Set(int &State,int bit,int val){bit=(bit-1)<<1;State|=3<<bit,State^=3<<bit,State|=val<<bit;} 3 inline int Link(int State,int pos) 4 { 5 int cnt=0,Delta=(Find(State,pos)==1)?1:-1;//這個變量決定向左尋找匹配仍是向右 6 for(int i=pos;i&&i<=m+1;i+=Delta) 7 { 8 int plug=Find(State,i); 9 if(plug==1)cnt++; 10 else if(plug==2)cnt--; 11 if(cnt==0)return i; 12 } 13 return -1; 14 }
有了上面這些操做,本題的所有代碼實現已經水到渠成了:咱們只須要把上面7種狀況一一對應實現便可。代碼見下:
(還有一個注意點,記得寫高精度!)
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <cmath> 5 using namespace std; 6 typedef long long LL; 7 const int cube=(int)1e9,mod=2601; 8 int n,m; 9 struct Data_Analysis 10 { 11 int bit[6]; 12 inline void Clear(){memset(bit,0,sizeof(bit));} 13 Data_Analysis(){Clear();} 14 inline void Set(int t){Clear();while(t)bit[++bit[0]]=t%cube,t/=cube;} 15 inline int &operator [](int x){return bit[x];} 16 inline void Print() 17 { 18 printf("%d",bit[bit[0]]); 19 for(int i=bit[0]-1;i>0;i--)printf("%09d",bit[i]); 20 printf("\n"); 21 } 22 inline Data_Analysis operator + (Data_Analysis b) 23 { 24 Data_Analysis c;c.Clear(); 25 c[0]=max(bit[0],b[0])+1; 26 for(int i=1;i<=c[0];i++) 27 c[i]+=bit[i]+b[i],c[i+1]+=c[i]/cube,c[i]%=cube; 28 while(!c[c[0]])c[0]--; 29 return c; 30 } 31 inline void operator += (Data_Analysis b){*this=*this+b;} 32 inline void operator = (int x){Set(x);} 33 }Ans; 34 struct Hash_Sheet 35 { 36 Data_Analysis val[mod]; 37 int key[mod],size,hash[mod]; 38 inline void Initialize() 39 { 40 memset(val,0,sizeof(val)),memset(key,-1,sizeof(key)); 41 size=0,memset(hash,0,sizeof(hash)); 42 } 43 inline void Newhash(int id,int v){hash[id]=++size,key[size]=v;} 44 Data_Analysis &operator [](const int State) 45 { 46 for(int i=State%mod;;i=(i+1==mod)?0:i+1) 47 { 48 if(!hash[i])Newhash(i,State); 49 if(key[hash[i]]==State)return val[hash[i]]; 50 } 51 } 52 }f[2]; 53 inline int Find(int State,int id){return (State>>((id-1)<<1))&3;} 54 inline void Set(int &State,int bit,int val){bit=(bit-1)<<1;State|=3<<bit,State^=3<<bit,State|=val<<bit;} 55 inline int Link(int State,int pos) 56 { 57 int cnt=0,Delta=(Find(State,pos)==1)?1:-1; 58 for(int i=pos;i&&i<=m+1;i+=Delta) 59 { 60 int plug=Find(State,i); 61 if(plug==1)cnt++; 62 else if(plug==2)cnt--; 63 if(cnt==0)return i; 64 } 65 return -1; 66 } 67 inline void Execution(int x,int y) 68 { 69 int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 70 f[now].Initialize(); 71 for(int i=1;i<=tot;i++) 72 { 73 int State=f[last].key[i]; 74 Data_Analysis Val=f[last].val[i]; 75 int plug1=Find(State,y),plug2=Find(State,y+1); 76 if(Link(State,y)==-1||Link(State,y+1)==-1)continue; 77 if(!plug1&&!plug2){if(x!=n&&y!=m)Set(State,y,1),Set(State,y+1,2),f[now][State]+=Val;} 78 else if(plug1&&!plug2) 79 { 80 if(x!=n)f[now][State]+=Val; 81 if(y!=m)Set(State,y,0),Set(State,y+1,plug1),f[now][State]+=Val; 82 } 83 else if(!plug1&&plug2) 84 { 85 if(y!=m)f[now][State]+=Val; 86 if(x!=n)Set(State,y,plug2),Set(State,y+1,0),f[now][State]+=Val; 87 } 88 else if(plug1==1&&plug2==1) 89 Set(State,Link(State,y+1),1),Set(State,y,0),Set(State,y+1,0),f[now][State]+=Val; 90 else if(plug1==1&&plug2==2){if(x==n&&y==m)Ans+=Val;} 91 else if(plug1==2&&plug2==1)Set(State,y,0),Set(State,y+1,0),f[now][State]+=Val; 92 else if(plug1==2&&plug2==2) 93 Set(State,Link(State,y),2),Set(State,y,0),Set(State,y+1,0),f[now][State]+=Val; 94 } 95 } 96 int main() 97 { 98 scanf("%d%d",&n,&m); 99 if(n==1||m==1){printf("1\n");return 0;} 100 if(m>n)swap(n,m); 101 f[0].Initialize();f[0][0]=1; 102 for(int i=1;i<=n;i++) 103 { 104 for(int j=1;j<=m;j++)Execution(i,j); 105 if(i!=n) 106 { 107 int now=(i*m)&1,tot=f[now].size; 108 for(int j=1;j<=tot;j++) 109 f[now].key[j]<<=2; 110 } 111 } 112 Ans+=Ans;Ans.Print(); 113 }
經過這道題的歷練,相信你對插頭DP的插頭定義,最小表示法,以及狀態優化的方法有了必定的瞭解。
尤爲須要培養的是插頭定義的「手感」,插頭定義絕對是你解題的關鍵。
接下來,咱們把目光轉移到「簡單路徑」上來。經過下面這道例題,相信會對這類簡單路徑&迴路問題有更深的理解。
Hnoi2007-Day1有一道題目 Park:給你一個 m * n 的矩陣,每一個矩陣內有個
權值V(i,j) (可能爲負數),要求找一條迴路,使得每一個點最多通過一次,而且通過
的點權值之和最大,想必你們印象深入吧.
無聊的小 C 同窗把這個問題稍微改了一下:要求找一條路徑,使得每一個點
最多通過一次,而且點權值之和最大,若是你跟小 C 同樣無聊,就麻煩作一下
這個題目吧.
第一行 m, n,接下來 m行每行 n 個數即V( i,j)
一個整數表示路徑的最大權值之和.
1 inline bool check(int State) 2 { 3 int cnt=0,cnt1=0; 4 for(int i=1;i<=m+1;i++) 5 { 6 int plug=Find(State,i); 7 if(plug==3)cnt++; 8 else if(plug==1)cnt1++; 9 else if(plug==2)cnt1--; 10 } 11 return cnt>2||cnt1!=0; 12 }
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int N=110,M=10,mod=16631; 6 int n,m,ans,v[N][M]; 7 struct Hash_System 8 { 9 int key[mod],size,hash[mod],val[mod]; 10 inline void Initialize() 11 { 12 memset(key,-1,sizeof(key));memset(val,0xaf,sizeof(val)); 13 size=0;memset(hash,0,sizeof(hash)); 14 } 15 inline void Newhash(int id,int State){hash[id]=++size;key[size]=State;} 16 inline int &operator [] (const int State) 17 { 18 for(int i=State%mod;;i=(i+1==mod)?0:i+1) 19 { 20 if(!hash[i])Newhash(i,State); 21 if(key[hash[i]]==State)return val[hash[i]]; 22 } 23 } 24 }f[2]; 25 inline int max(int a,int b){return a>b?a:b;} 26 inline int Find(int State,int pos){return (State>>((pos-1)<<1))&3;} 27 inline void Set(int &State,int pos,int val){pos=(pos-1)<<1,State|=(3<<pos),State^=(3<<pos),State^=(val<<pos);} 28 inline int Link(int State,int pos) 29 { 30 int cnt=0,Delta=(Find(State,pos)==1)?1:-1; 31 for(int i=pos;i&&i<=m+1;i+=Delta) 32 { 33 int plug=Find(State,i); 34 if(plug==1)cnt++; 35 else if(plug==2)cnt--; 36 if(cnt==0)return i; 37 } 38 return -1; 39 } 40 inline bool check(int State) 41 { 42 int cnt=0,cnt1=0; 43 for(int i=1;i<=m+1;i++) 44 { 45 int plug=Find(State,i); 46 if(plug==3)cnt++; 47 else if(plug==1)cnt1++; 48 else if(plug==2)cnt1--; 49 } 50 return cnt>2||cnt1!=0; 51 } 52 inline void Execution(int x,int y) 53 { 54 int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 55 f[now].Initialize(); 56 for(int i=1;i<=tot;i++) 57 { 58 int State=f[last].key[i],Val=f[last].val[i]; 59 if (check(State)||State>=(1<<((m+1)<<1)))continue; 60 int plug1=Find(State,y),plug2=Find(State,y+1); 61 int ideal=State;Set(ideal,y,0),Set(ideal,y+1,0);//ideal表明去掉y-1,y兩個插頭以後的輪廓線狀態。 62 int empty1=ideal,empty2=ideal; 63 if(!plug1&&!plug2) 64 { 65 f[now][ideal]=max(f[now][ideal],Val); 66 if(x<n&&y<m)Set(State,y,1),Set(State,y+1,2),f[now][State]=max(f[now][State],Val+v[x][y]); 67 if(x<n)Set(empty1,y,3),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 68 if(y<m)Set(empty2,y+1,3),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 69 } 70 else if(plug1&&!plug2) 71 { 72 if(x<n)f[now][State]=max(f[now][State],Val+v[x][y]); 73 if(y<m)Set(empty1,y+1,plug1),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 74 if(plug1==3){if(!ideal)ans=max(ans,Val+v[x][y]);} 75 else Set(empty2,Link(State,y),3),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 76 } 77 else if(!plug1&&plug2) 78 { 79 if(y<m)f[now][State]=max(f[now][State],Val+v[x][y]); 80 if(x<n)Set(empty2,y,plug2),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 81 if(plug2==3){if(!ideal)ans=max(ans,Val+v[x][y]);} 82 else Set(empty1,Link(State,y+1),3),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 83 } 84 else if(plug1==1&&plug2==1)Set(empty1,Link(State,y+1),1),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 85 else if(plug1==1&&plug2==2)continue; 86 else if(plug1==2&&plug2==1)f[now][ideal]=max(f[now][ideal],Val+v[x][y]); 87 else if(plug1==2&&plug2==2)Set(empty2,Link(State,y),2),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 88 else if(plug1==3&&plug2==3){if(!ideal)ans=max(ans,Val+v[x][y]);} 89 else if(plug2==3)Set(empty1,Link(State,y),3),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 90 else if(plug1==3)Set(empty2,Link(State,y+1),3),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 91 } 92 } 93 int main() 94 { 95 scanf("%d%d",&n,&m); 96 for(register int i=1;i<=n;i++) 97 for(register int j=1;j<=m;j++) 98 scanf("%d",&v[i][j]),ans=max(ans,v[i][j]); 99 f[0].Initialize();f[0][0]=0; 100 for(register int i=1;i<=n;i++) 101 { 102 for(register int j=1;j<=m;j++)Execution(i,j); 103 if(i!=n) 104 for(int j=1,last=(i*m)&1,tot=f[last].size;j<=tot;j++) 105 f[last].key[j]<<=2; 106 } 107 printf("%d\n",ans); 108 }
lxhgww的小名叫「小L」,這是由於他老是很喜歡L型的東西。小L家的客廳是一個矩形,如今他想用L型的地板來鋪滿整個客廳,客廳裏有些位置有柱子,不能鋪地板。如今小L想知道,用L型的地板鋪滿整個客廳有多少種不一樣的方案?
須要注意的是,以下圖所示,L型地板的兩端長度能夠任意變化,但不能長度爲0。鋪設完成後,客廳裏面全部沒有柱子的地方都必須鋪上地板,但同一個地方不能被鋪屢次。
輸入的第一行包含兩個整數,R和C,表示客廳的大小。
接着是R行,每行C個字符。’_’表示對應的位置是空的,必須鋪地板;’*’表示對應的位置有柱子,不能鋪地板。
輸出一行,包含一個整數,表示鋪滿整個客廳的方案數。因爲這個數可能很大,只需輸出它除以20110520的餘數。
R*C<=100
看到這道題,咱們很容易發現上面幾道題的全部插頭定義方式都不在適用了。
所以,咱們須要自行尋找到新的插頭定義方式。
容易注意到,和上題的「路徑」相比,本題的合法路徑「L型地板」有一些特殊的地方:拐彎且僅拐彎一次。
這因爲一條路徑只有兩種狀態:拐彎過和沒拐彎過,所以咱們能夠嘗試着這樣定義新的插頭:
咱們使用三進制,0表明沒有插頭,1表明沒拐彎過的路徑,2表明已經拐彎過的路徑。
依然設當前轉移到格子(x,y),設y-1號插頭狀態爲p1,y號插頭狀態爲p2。
那麼會有下面的幾種狀況:
狀況1:p1==0&&p2==0
這時咱們有三種可選的策略:
①以當前位置爲起點,從p1方向引出一條新的路徑(把p1修改成1號插頭)
②以當前位置爲起點,從p2方向引出一條新的路徑(把p2修改成1號插頭)
③以當前位置爲「L」型路徑的轉折點,向p1,p2兩個方向均引出一個2號插頭.
狀況2:p1==0&&p2==1
因爲p2節點尚未拐過彎,所以咱們有2種可選的策略:
①繼續直走,不拐彎,即上->下(把p1修改成1號插頭,p2置0)
②選擇拐彎,即上->右(把p2改成2號插頭)
狀況3:p1==1&&p2==0
這種狀況和狀況2相似,再也不贅述。
狀況4:p1==0&&p2==2
因爲p2節點已經拐過彎,因此咱們有以下的兩種策略:
狀況5:p1==2&&p2==0
這種狀況與狀況4相似,再也不贅述。
狀況6:p1==1&&p2==1
這種狀況下,兩塊地板均沒有拐過彎,所以咱們能夠在本格將這兩塊地板合併,造成一個合法的「L」型路徑,並將本格看作他們的轉折點。(把p1和p2都置爲0)
至此,新插頭定義的狀態轉移已經討論完成。
不難發現,這種新的插頭定義能夠處理可能發生的全部可行狀況。
咱們只須要把他們轉化爲代碼實現便可,具體見下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int N=105,HASH=200097,mod=20110520; 6 int ans,n,m,last_x,last_y;char c[N][N];bool room[N][N]; 7 struct Hash_System 8 { 9 int val[HASH],key[HASH],hash[HASH],size; 10 inline void Initialize() 11 { 12 memset(val,0,sizeof(val)),memset(hash,0,sizeof(hash)); 13 memset(key,-1,sizeof(key)),size=0; 14 } 15 inline void Newhash(int id,int State){hash[id]=++size,key[size]=State;} 16 inline int &operator [] (const int State) 17 { 18 for(register int i=State%HASH;;i=(i+1==HASH)?0:i+1) 19 { 20 if(!hash[i])Newhash(i,State); 21 if(key[hash[i]]==State)return val[hash[i]]; 22 } 23 } 24 }f[2]; 25 inline int Find(int State,int pos){return (State>>((pos-1)<<1))&3;} 26 inline void Set(int &State,int pos,int val) 27 {pos=(pos-1)<<1,State|=(3<<pos),State^=(3<<pos),State^=(val<<pos);} 28 inline void Print(int State){for(int i=0;i<=m;i++)printf("%d",(State>>(i<<1))&3);} 29 inline void Execution(int x,int y) 30 { 31 register int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 32 f[now].Initialize(); 33 for(register int i=1;i<=tot;i++) 34 { 35 int State=f[last].key[i],Val=f[last].val[i]; 36 int plug1=Find(State,y),plug2=Find(State,y+1); 37 if(room[x][y]) 38 { 39 if(!plug1&&!plug2) 40 { 41 if(room[x+1][y])Set(State,y,1),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 42 if(room[x][y+1])Set(State,y,0),Set(State,y+1,1),f[now][State]=(f[now][State]+Val)%mod; 43 if(room[x][y+1]&&room[x+1][y])Set(State,y,2),Set(State,y+1,2),f[now][State]=(f[now][State]+Val)%mod; 44 } 45 else if(!plug1&&plug2) 46 { 47 if(plug2==1) 48 { 49 if(room[x+1][y])Set(State,y,1),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 50 if(room[x][y+1])Set(State,y,0),Set(State,y+1,2),f[now][State]=(f[now][State]+Val)%mod; 51 } 52 else 53 { 54 Set(State,y,0),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 55 if(x==last_x&&y==last_y&&!State)ans=(ans+Val)%mod; 56 if(room[x+1][y])Set(State,y,2),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 57 } 58 } 59 else if(plug1&&!plug2) 60 { 61 if(plug1==1) 62 { 63 if(room[x][y+1])Set(State,y,0),Set(State,y+1,1),f[now][State]=(f[now][State]+Val)%mod; 64 if(room[x+1][y])Set(State,y,2),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 65 } 66 else 67 { 68 Set(State,y,0),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 69 if(x==last_x&&y==last_y&&!State)ans=(ans+Val)%mod; 70 if(room[x][y+1])Set(State,y,0),Set(State,y+1,2),f[now][State]=(f[now][State]+Val)%mod; 71 } 72 } 73 else if(plug1==1&&plug2==1) 74 { 75 Set(State,y,0),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 76 if(x==last_x&&y==last_y&&!State)ans=(ans+Val)%mod; 77 } 78 } 79 else if(!plug1&&!plug2)f[now][State]=(f[now][State]+Val)%mod; 80 } 81 } 82 int main() 83 { 84 scanf("%d%d",&n,&m); 85 for(register int i=1;i<=n;i++)scanf("%s",c[i]+1); 86 for(register int i=1;i<=n;i++) 87 for(register int j=1;j<=m;j++)room[i][j]=(c[i][j]=='*')?0:1; 88 if(m>n) 89 { 90 for(register int i=1;i<=n;i++) 91 for(register int j=i+1;j<=m;j++) 92 swap(room[i][j],room[j][i]); 93 swap(n,m); 94 } 95 for(register int i=1;i<=n;i++) 96 for(register int j=1;j<=m;j++) 97 if(room[i][j])last_x=i,last_y=j; 98 f[0].Initialize();f[0][0]=1; 99 for(register int i=1;i<=n;i++) 100 { 101 for(register int j=1;j<=m;j++)Execution(i,j); 102 if(i!=n) 103 for(register int j=1,last=(i*m)&1,tot=f[last].size;j<=tot;j++) 104 f[last].key[j]<<=2; 105 } 106 printf("%d\n",ans); 107 }
面對一道以前沒有見過的問題,咱們經過尋找插頭的新定義成功解決了該題。
相信你已經對插頭定義有了初步的瞭解,接下來,必定要在作題時繼續培養這種能力,這樣才能百戰不殆。
下面,再給出幾道不是那麼簡單的題目,有興趣的讀者能夠試着作一下:
BZOJ1187.[HNOI2007]神奇遊樂園
BZOJ2595[Wc2008]遊覽計劃
POJ3133Manhattan Wiring
POJ1739 Tony's Tour
上面講解的4道題都是插頭DP中比較基礎的問題,但各自都體現出了不一樣的側重點。
插頭DP是一類很美妙的問題,當你看到本身辛苦想出、碼出的分類討論AC的時候,心中必定會有很大的成就感吧!
但願讀完這篇博文的你能有所收穫,對插頭DP有更深入的瞭解!