1. 程式人生 > 其它 >P5911 [POI2004]PRZ 題解

P5911 [POI2004]PRZ 題解

N=16,首先考慮將其作為狀壓維度

可以用其來記錄哪一些人已經渡河,哪一些人沒有渡河。

每一次狀態轉移就是一次渡河,可以暴力列舉本次渡河的人是哪一些。

利用記憶化搜尋可以更好地實現這個過程。

在列舉上一個合法狀態的時候可以利用列舉子集的方式來完成,以此降低一部分複雜度。實際上列舉子集的複雜度依然是2^n

列舉子集的方法看其他blog

程式碼如下

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace
std; 6 inline int r() 7 { 8 int s=0,k=1;char c=getchar(); 9 while(!isdigit(c)) 10 { 11 if(c=='-')k=-1; 12 c=getchar(); 13 } 14 while(isdigit(c)) 15 { 16 s=s*10+c-'0'; 17 c=getchar(); 18 } 19 return s*k; 20 } 21 int m,n,t[20],w[20],f[66666
],chk[66666],cst[66666]; 22 bool check(int x) 23 { 24 //if(chk[x]!=-1)return chk[x]; 25 int now=0,nd=0; 26 while(x) 27 { 28 now++; 29 if(x&1)nd+=w[now]; 30 if(nd>m)return chk[x]=0; 31 x>>=1; 32 } 33 return chk[x]=1; 34 } 35 int cost(int x)
36 { 37 //if(cst[x]!=-1)return cst[x]; 38 int now=0,nd=0; 39 while(x) 40 { 41 now++; 42 if(x&1)nd=max(nd,t[now]); 43 x>>=1; 44 } 45 return cst[x]=nd; 46 } 47 int dfs(int state) 48 { 49 if(!state)return 0; 50 if(f[state])return f[state]; 51 int mini=1e9; 52 for(int i=state;i;i=(i-1)&state) 53 if(check(i)) 54 { 55 mini=min(mini,dfs(state^i)+cost(i)); 56 } 57 // cout<<"state: "<<state<<" "<<mini<<endl; 58 return f[state]=mini; 59 } 60 int main() 61 { 62 memset(chk,-1,sizeof(chk)); 63 memset(cst,-1,sizeof(cst)); 64 m=r();n=r(); 65 for(int i=1;i<=n;i++) 66 { 67 t[i]=r(); 68 w[i]=r(); 69 } 70 for(int i=1;i<=n;i++) 71 f[1<<(i-1)]=t[i]; 72 for(int i=1;i<=n;i++) 73 for(int j=1;j<=n;j++) 74 { 75 if(i!=j) 76 { 77 int tmp=(1<<(i-1))|(1<<(j-1)); 78 if(check(tmp))f[tmp]=cost(tmp); 79 else f[tmp]=t[i]+t[j]; 80 } 81 } 82 cout<<dfs((1<<n)-1); 83 }