DP畢竟是算法中最精妙的部分,理解並玩得花哨還是需要一定的時間積累
之前對普通的DP也不敢說掌握,只能說略懂皮毛
在學習樹形DP 的同時也算是對DP有了更深的理解吧
DP的關鍵就在於狀態的定義以及找轉移
首先要考慮清楚狀態,狀態要能夠很好地並且完整地描述子問題
其次考慮最底層的狀態,這些狀態一般是最簡單的情況或者是邊界情況
再就是考慮某一個狀態能從哪些子狀態轉移過來,同時還要考慮轉移的順序,確保子問題已經解決
樹形DP很多時候就是通過子節點推父親節點的狀態
還是通過題目來加強理解吧
1.HDU 1520
題意:給一棵樹,選最多的結點使得選擇的結點不存在直接的父子關系
很容易想到一個結點有兩個狀態:選或者不選
所以自然地想到狀態dp[u][0/1]表示u子樹內的最佳答案,u的狀態為選或者不選
初始化自然是葉子結點dp[u][0]=0,dp[u][1]=w[u]
轉移則可以考慮依次考慮
u不選的時候:u的兒子可以任意選或者不選,所以dp[u][0]+=max(dp[v][0],dp[v][1])
u選的時候:u的兒子必定不能選,所以dp[u][1]+=dp[v][0] 然后dp[u][1]+=w[u]表示加上u這個點
答案自然就是max(dp[rt][0],dp[rt][1])了

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 6005; const int M = 1e5+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N],w[N],deg[N]; int dp[N][2]; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[M]; void init(){ tot=0; mems(first,-1); mems(deg,0); mems(dp,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; } void dfs(int u){ dp[u][0]=0; dp[u][1]=w[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; dfs(v); dp[u][0]+=max(dp[v][1],dp[v][0]); dp[u][1]+=dp[v][0]; } } int n; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d",&n)){ init(); for(int i=1;i<=n;i++) scanf("%d",&w[i]); int u,v; while(1){ scanf("%d%d",&v,&u); if(!v&&!u) break; addedge(u,v);deg[v]++; } int rt; for(int i=1;i<=n;i++) if(!deg[i]){ dfs(rt=i); break; } printf("%d\n",max(dp[rt][0],dp[rt][1])); } return 0; }
2.POJ 1436
題意:選中一個點則與其相連的邊將被覆蓋,問最少選幾個點使得樹中所有邊均被覆蓋
和上一個題很類似
同樣狀態設為dp[u][0/1]
初始的底層狀態自然是dp[u][0]=0,dp[u][1]=1;
考慮一個非葉子結點和它兒子的所有連邊
如果當前結點不選,那這些邊只能由其兒子保護,所以dp[u][0]+=dp[v][1]
如果當前結點選,那這些邊已被保護,其兒子選和不選都行,所以dp[u][1]+=min(dp[v][0],dp[v][1])
答案自然是min(dp[rt][0],dp[rt][1])

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1505; const int M = 1e5+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N],deg[N]; int dp[N][2]; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[M]; void init(){ tot=0; mems(first,-1); mems(deg,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; } void dfs(int u){ dp[u][0]=0; dp[u][1]=1; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; dfs(v); dp[u][0]+=dp[v][1]; dp[u][1]+=min(dp[v][0],dp[v][1]); } } int n,k,u,v; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d",&n)){ init(); for(int i=1;i<=n;i++){ scanf("%d:(%d)",&u,&k); for(int j=0;j<k;j++){ scanf("%d",&v); addedge(u,v);deg[v]++; } } int rt; for(int i=0;i<n;i++) if(!deg[i]){ dfs(rt=i); break; } printf("%d\n",min(dp[rt][0],dp[rt][1])); } return 0; }
3.URAL 1018
題意:樹中每個點有權值,問只留下k個點剩下的最大權值和是多少?留下的點還是構成一棵樹
樹形背包
狀態定義成dp[u][i]表示u子樹內剩i個點的最大權值
考慮葉子結點:dp[u][0]=0,dp[u][1]=w[u]
考慮非葉子結點的一個狀態dp[u][i],對於當前的一個兒子v,枚舉一個k表示從這個兒子里取幾個結點,維護一個最大值
其實我們這里的狀態是三維的,表示u子樹的前j個子樹取了i個結點的答案
我們使用滾動數組把j這一維滾掉
這里簡化了題目,每一個結點固定只有兩個兒子,用一般做法做肯定也是沒問題的
還有要注意的地方就是這里根是一定要保留的
處理方法就是對於狀態dp[u][1]直接賦值,枚舉時候i從2開始,這樣就可以默認根已取

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 105; const int M = 1e5+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N],w[N]; int dp[N][N],sz[N]; int ls[N],rs[N]; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[M]; void init(){ tot=0; mems(first,-1); mems(w,-1); //mems(deg,0); mems(dp,0); mems(ls,-1); mems(rs,-1); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } void dfs1(int u,int fa){ sz[u]=1; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); sz[u]+=sz[v]; if(ls[u]==-1) ls[u]=v; else rs[u]=v; } } void dfs(int u){ int f=0; dp[u][0]=0;dp[u][1]=w[u]; if(ls[u]!=-1) dfs(ls[u]),f=1; if(rs[u]!=-1) dfs(rs[u]),f=1; if(!f) return; for(int i=2;i<=sz[u];i++) for(int j=0;j<=sz[ls[u]];j++) if(i-1>=j) dp[u][i]=max(dp[u][i],dp[ls[u]][j]+dp[rs[u]][i-1-j]+w[u]); } int n,k; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&k)){ init(); int u,v,x,rt=1;w[rt]=0; for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&x); addedge(u,v); if(w[v]==-1) w[v]=x; else w[u]=x; } dfs1(rt,-1); dfs(rt); printf("%d\n",dp[rt][k+1]); } return 0; }
4.HDU 2196
題意:對於樹中的每一個結點,輸出樹中與其距離最遠的結點的距離值
經典的樹形DP問題
狀態定義為dp[u][0/1]為u到其子樹內結點的最遠距離、次遠距離
這樣一輪dp下來,可以想到對於根來說,dp[rt][0]就是它的答案
但是對於其它結點來說只得到了其子樹內的答案,而我們需要的是其對於整棵樹的信息
這里需要再一次dfs,相當於反過來從根往葉子再dp一次,通過根的答案推其余結點的答案
這里之所以要維護一個次大值,就是對於一個結點u的兒子v,
若u的最遠距離是經過u的,那v的答案應該是v子樹內的答案和u的次大值比較,否則v的答案和u的最大值比較
畫個圖就明白了

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e4+5; const int M = 2e4+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N]; int mx[N][2],id[N][2]; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[M]; void init(){ tot=0; mems(first,-1); mems(mx,0); mems(id,-1); } void addedge(int u,int v,int w){ edge[tot]=node(v,first[u],w); first[u]=tot++; edge[tot]=node(u,first[v],w); first[v]=tot++; } void dfs1(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); if(mx[v][0]+edge[i].w>=mx[u][0]){ mx[u][1]=mx[u][0]; id[u][1]=id[u][0];id[u][0]=v; mx[u][0]=mx[v][0]+edge[i].w; } else if(mx[v][0]+edge[i].w>mx[u][1]) mx[u][1]=mx[v][0]+edge[i].w,id[u][1]=v; } } void dfs2(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; if(id[u][0]==v){ if(mx[v][1]<mx[u][1]+edge[i].w){ mx[v][1]=mx[u][1]+edge[i].w; id[v][1]=u; } } else{ if(mx[v][1]<mx[u][0]+edge[i].w){ mx[v][1]=edge[i].w+mx[u][0]; id[v][1]=u; } } if(mx[v][0]<mx[v][1]){ swap(mx[v][0],mx[v][1]); swap(id[v][0],id[v][1]); } dfs2(v,u); } } int n,u,w; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d",&n)){ init(); for(int i=2;i<=n;i++){ scanf("%d%d",&u,&w); addedge(i,u,w); } dfs1(1,-1); dfs2(1,-1); for(int i=1;i<=n;i++) printf("%d\n",mx[i][0]); } return 0; }
5.POJ 2152
題意:樹中每個結點有兩個值:w[i]表示在i建設防火設施的價格,d[i]表示與i最近的防火設施的距離上限,求滿足所有d[i]的最小花費
算是一道比較難的樹形dp,狀態和普通的樹形DP略有不同
樹形dp很多時候是把一個結點及其子樹看成一個整體,然后考慮這個結點的狀態進行轉移
考慮到數據范圍N<=1000,可以定義狀態dp[u][i]表示u依靠i時,保證子樹內安全的最小花費
為了轉移方便,再定義all[u]表示保證u的安全的最小花費
其實可以理解為all[u]是在dp[u][i]中取了個最優值
要確定一個點是否能被u依靠就需要知道u與該點的距離
所以先n^2處理樹中任意兩點的距離
考慮葉子結點:dp[u][i]=w[i]
考慮一個非葉子結點u,先枚舉它依靠的結點i
再考慮u的兒子v,v可以選擇依靠或者不依靠i,則dp[u][i]+=min(dp[v][i]-c[i],all[v])
對於u的每一個i更新u的最優解all[u]
對於u的孫子k是不需要考慮的,因為k依靠i的情況在解決v的時候已經考慮到了,所以不會有重復計算的情況

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #define ls pos<<1 #define rs pos<<1|1 #define lson L,mid,pos<<1 #define rson mid+1,R,pos<<1|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e3+5; const int M = 2e3+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N]; int dp[N][N],all[N]; int n,u,v,x; int cost[N],d[N],dis[N][N]; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[M]; void init(){ tot=0; mems(first,-1); mems(all,INF); mems(dp,INF); } void addedge(int u,int v,int w){ edge[tot]=node(v,first[u],w); first[u]=tot++; edge[tot]=node(u,first[v],w); first[v]=tot++; } void dfs1(int rt,int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dis[rt][v]=dis[rt][u]+edge[i].w; dfs1(rt,v,u); } } void dfs2(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs2(v,u); } for(int k=1;k<=n;k++) if(dis[u][k]<=d[u]){ dp[u][k]=cost[k]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dp[u][k]+=min(dp[v][k]-cost[k],all[v]); } all[u]=min(all[u],dp[u][k]); } } int T; int main(){ //freopen("in.txt","r",stdin); for(int i=0;i<N;i++) dis[i][i]=0; scanf("%d",&T); while(T--){ init(); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&cost[i]); for(int i=1;i<=n;i++) scanf("%d",&d[i]); for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&x); addedge(u,v,x); } for(int i=1;i<=n;i++) dfs1(i,i,-1); dfs2(1,-1); printf("%d\n",all[1]); } return 0; }
6.POJ 3162
題意:對於樹中每一個結點i找到另一個結點使得兩者距離dp[i]最遠,最后輸出一段最長區間的長度,區間maxv-minv<=M
只是在樹形dp上加了點東西而已,用線段樹+two pointer維護就好了

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #define ls pos<<1 #define rs pos<<1|1 #define lson L,mid,pos<<1 #define rson mid+1,R,pos<<1|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e6+5; const int M = 2e6+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N]; int dp[N][2],id[N][2]; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[M]; void init(){ tot=0; mems(first,-1); } void addedge(int u,int v,int w){ edge[tot]=node(v,first[u],w); first[u]=tot++; edge[tot]=node(u,first[v],w); first[v]=tot++; } void update(int u){ if(dp[u][0]<dp[u][1]){ swap(dp[u][0],dp[u][1]); swap(id[u][0],id[u][1]); } } void dfs1(int u,int fa){ dp[u][1]=dp[u][0]=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); if(edge[i].w+dp[v][0]>dp[u][1]){ dp[u][1]=edge[i].w+dp[v][0]; id[u][1]=v; } update(u); } } void dfs2(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; if(id[u][0]==v){ if(dp[u][1]+edge[i].w>dp[v][1]){ dp[v][1]=dp[u][1]+edge[i].w; id[v][1]=u; } } else{ if(dp[u][0]+edge[i].w>dp[v][1]){ dp[v][1]=dp[u][0]+edge[i].w; id[v][1]=u; } } update(v); dfs2(v,u); } } int minv[N<<2],maxv[N<<2]; void build(int L,int R,int pos){ if(L==R){ minv[pos]=maxv[pos]=dp[L][0]; return; } mdzz; build(lson); build(rson); minv[pos]=min(minv[ls],minv[rs]); maxv[pos]=max(maxv[ls],maxv[rs]); } int query_min(int l,int r,int L,int R,int pos){ if(l<=L&&R<=r) return minv[pos]; mdzz; int ans=INF; if(l<=mid) ans=min(ans,query_min(l,r,lson)); if(r>mid) ans=min(ans,query_min(l,r,rson)); return ans; } int query_max(int l,int r,int L,int R,int pos){ if(l<=L&&R<=r) return maxv[pos]; mdzz; int ans=-INF; if(l<=mid) ans=max(ans,query_max(l,r,lson)); if(r>mid) ans=max(ans,query_max(l,r,rson)); return ans; } int n,m,u,v; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ init(); for(int i=2;i<=n;i++){ scanf("%d%d",&u,&v); addedge(i,u,v); } dfs1(1,-1); dfs2(1,-1); int ans=0; build(1,n,1); int l=1,r=1; while(1){ if(l>r||l>n||r>n) break; int mxv=query_max(l,r,1,n,1); int miv=query_min(l,r,1,n,1); if(mxv-miv<=m){ ans=max(ans,r-l+1); r++; } else l++; } printf("%d\n",ans); } return 0; }
7.codeforces 219D
題意:樹邊有方向,選擇一個點,翻轉最少路徑,使得其能到達其余所有的點,輸出所有可能的答案
可以將翻轉理解為一種花費,那不翻轉就是花費0,翻轉則為1
可以定義dp[u]表示u到所有點的距離和,那dp[u]最小的就是答案
依舊先考慮u的子樹內的答案
考慮一個葉子:dp[u]=0;
考慮一個非葉子u以及其的一個兒子v:很容易想到dp[u]+=dp[v]+w[u,v]
一次dfs下來后rt的答案已知,接下來通過rt來推其余結點對於整棵樹的答案
考慮結點u及其一個兒子v,v到v子樹內的答案已知,v到u除v子樹的結點的答案是dp[u]-dp[v]-w[u,v]
所以dp[v]+=dp[u]-dp[v]-w[u,v]+w[v,u]

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 2e5+5; const int M = 4e5+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N]; int dp[N],sz[N]; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[M]; void init(){ tot=0; mems(first,-1); } void addedge(int u,int v){ edge[tot]=node(v,first[u],0); first[u]=tot++; edge[tot]=node(u,first[v],1); first[v]=tot++; } void dfs1(int u,int fa){ dp[u]=0;sz[u]=1; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); sz[u]+=sz[v]; dp[u]+=dp[v]+edge[i].w; } } void dfs2(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dp[v]+=(dp[u]-dp[v]-edge[i].w)+(edge[i].w^1); dfs2(v,u); } } int n,u,v; int main(){ init(); scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } dfs1(1,-1); dfs2(1,-1); int ans=INF; for(int i=1;i<=n;i++) ans=min(ans,dp[i]); //for(int i=1;i<=n;i++) cout<<i<<' '<<dp[i]<<endl; printf("%d\n",ans); for(int i=1;i<=n;i++) if(ans==dp[i]) printf("%d ",i); return 0; }
8.POJ 1155
題意:樹中每個葉子有點權表示收入,邊權表示花費。選擇某些葉子后,不必要的邊可刪掉,最多選擇幾個點使得花費>=收入
狀態很多時候是根據要求的東西來定義的
這里定義dp[u][i]為子樹u取i個葉子的最優解
由於不選葉子的話是不需要任何花費的,所以容易想到dp[u][0]=0
考慮一個葉子,其第二維只有0/1兩種取值,1的話明顯是dp[u][1]=w[u]
考慮一個非葉子,第二維1~sz[u]都是無法確定的狀態,而且考慮到結果可能是負值,而且我們需要的是一個最大值,所以初始化為-INF
對於一個結點u的一個兒子v,枚舉在這個兒子中取的葉子數k,維護個最優解就好了

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #define ls pos<<1 #define rs pos<<1|1 #define lson L,mid,pos<<1 #define rson mid+1,R,pos<<1|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 3e3+5; const int M = 2e3+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[N<<1]; int tot; int first[N],w[N],dp[N][N],sz[N]; void init(){ tot=0; mems(first,-1); mems(w,0); } void addedge(int u,int v,int w){ edge[tot]=node(v,first[u],w); first[u]=tot++; edge[tot]=node(u,first[v],w); first[v]=tot++; } void dfs1(int u,int fa){ int f=0;sz[u]=0;dp[u][0]=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; f=1; dfs1(v,u); sz[u]+=sz[v]; } if(!f){ sz[u]=1;dp[u][1]=w[u]; } else{ for(int i=1;i<=sz[u];i++) dp[u][i]=-INF; } } void dfs2(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs2(v,u); for(int j=sz[u];j>=1;j--) for(int k=1;k<=sz[v];k++) if(j>=k) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-edge[i].w); } } int n,m,u,v,k; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ init(); for(int i=1;i<=n-m;i++){ scanf("%d",&k); for(int j=0;j<k;j++){ scanf("%d%d",&u,&v); addedge(i,u,v); } } for(int i=n-m+1;i<=n;i++) scanf("%d",&w[i]); dfs1(1,-1); dfs2(1,-1); for(int i=m;i>=0;i--) if(dp[1][i]>=0){ printf("%d\n",i); break; } } return 0; }
9.HDU 1011
題意:樹中每一個結點有一個花費一個收益,問K元能取得的最大收益是多少
明顯的樹形背包,一個結點可以看成一個物品
XJB搞搞就行了
有個坑是a/20向上取整不能寫成(a-1)/20+1,a可能是0

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #define ls pos<<1 #define rs pos<<1|1 #define lson L,mid,pos<<1 #define rson mid+1,R,pos<<1|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 105; const int M = 2e3+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[N<<1]; int tot,n,m; int first[N],c[N],p[N],dp[N][N]; void init(){ tot=0; mems(first,-1); mems(dp,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } void dfs1(int u,int fa){ int x=(c[u]+19)/20; for(int i=x;i<=m;i++) dp[u][i]=p[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); for(int j=m;j>=x;j--) for(int k=1;k<=j-x;k++) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]); } } int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ if(n==-1&&m==-1) break; init(); for(int i=1;i<=n;i++) scanf("%d%d",&c[i],&p[i]); int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } if(!m){ puts("0"); continue; } dfs1(1,-1); printf("%d\n",dp[1][m]); } return 0; }
10.POJ 1947
題意:問最少需要破壞多少條邊能產生一個大小為K的塊
dp[u][i]表示u的子樹中產生一個i大小的塊需要破壞的最少邊數
num[u]表示u的兒子數
這題考慮的方向不同平常
考慮塊的大小1~sz[u] (0的大小是不合法的狀態)
若要在u的子樹中產生一個1大小的塊,初始情況應該是u和所有兒子的連邊斷開,所以dp[u][1]=num[u]
其實這樣處理相當於對於每一個狀態來說u都是默認取了的
考慮一個結點u及其一個兒子v,對於狀態dp[u][i] 枚舉在v中取的結點數k
最后要枚舉一個塊的根,因為我們之前處理狀態的時候是默認根取,所以最優解還可能在子樹中
如果加一維狀態[0/1]表示u是否取的話應該可以避免這個問題,不過也沒有去嘗試了

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #define ls pos<<1 #define rs pos<<1|1 #define lson L,mid,pos<<1 #define rson mid+1,R,pos<<1|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 155; const int M = 2e3+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[N<<1]; int tot,n,m; int first[N],dp[N][N],sz[N],num[N]; void init(){ tot=0; mems(first,-1); mems(dp,INF); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } void dfs1(int u,int fa){ sz[u]=1;num[u]=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); num[u]++; sz[u]+=sz[v]; } } void dfs2(int u,int fa){ dp[u][1]=num[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs2(v,u); for(int j=m;j>=1;j--) for(int k=1;k<=sz[v];k++) if(j-k>=1) dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1); } } int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ init(); int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } dfs1(1,-1); dfs2(1,-1); /*for(int i=1;i<=n;i++){ cout<<i<<":"<<endl; for(int j=1;j<=sz[i];j++) cout<<dp[i][j]<<' '; cout<<endl; }*/ int ans=dp[1][m]; for(int i=2;i<=n;i++) ans=min(ans,dp[i][m]+1); printf("%d\n",ans); } return 0; }
11.HDU 1561
題意:每個結點有一個權值,問選擇K個結點(必須按路徑選擇)獲得的最大權值
典型的樹形背包
由於不是任意選,我們只需要在狀態轉移的時候變通一下就好了
對於u來說,我要選擇其子孫的話前提是u選擇
所以對第二維i==1的時候單獨處理,而且之后不再更新這個值就能保證u是默認被選擇的

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #define ls pos<<1 #define rs pos<<1|1 #define lson L,mid,pos<<1 #define rson mid+1,R,pos<<1|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 205; const int M = 2e3+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[N]; int tot,n,m; int first[N],dp[N][N],w[N]; void init(){ tot=0; mems(first,-1); mems(dp,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; //edge[tot]=node(u,first[v]); // first[v]=tot++; } void dfs(int u){ dp[u][1]=w[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; dfs(v); for(int j=m;j>=2;j--) for(int k=0;k<=m;k++) if(j-1>=k) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]); } } int main(){ //freopen("in.txt","r",stdin); w[0]=0; while(scanf("%d%d",&n,&m)&&(n||m)){ init();m++; int rt=0,u; for(int i=1;i<=n;i++){ scanf("%d%d",&u,&w[i]); addedge(u,i); } dfs(rt); printf("%d\n",dp[rt][m]); } return 0; }
12.HDU 4003
題意:樹中每條邊有花費,有k個機器人,問遍歷所有的點的最少花費(可以回頭,每次只能從s出發
這一題的狀態定義很有意思
dp[u][i]表示遍歷完u的子樹后有i個機器人沒有回到u結點的最優解
對於每一棵子樹都是需要遍歷完的,所以必須選擇一個方案
為了保證至少選擇一個方案,所以在考慮當前兒子v的時候先將答案加上一個狀態,這里是加上沒有機器人留在v子樹的方案
然后再對v做一個背包,若有更優解則初始放置的選擇將被替換掉
其實相當於對於子樹v有k件物品來選擇,必須選擇一件

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e4+5; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[N<<1]; int tot; int first[N],dp[N][12]; void init(){ tot=0; mems(first,-1); mems(dp,0); } void addedge(int u,int v,int w){ edge[tot]=node(v,first[u],w); first[u]=tot++; edge[tot]=node(u,first[v],w); first[v]=tot++; } int n,s,m; void dfs(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs(v,u); for(int j=m;j>=0;j--){ dp[u][j]+=dp[v][0]+edge[i].w*2; for(int k=1;k<=j;k++) dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]+edge[i].w*k); } } } int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d%d",&n,&s,&m)){ init(); int u,v,w; for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); } dfs(s,-1); printf("%d\n",dp[s][m]); } return 0; }
13.HDU 4276
題意:每個點有點權,邊有花費,問T時間能否從1到達n,若能,能獲取的最大權值是多少
首先想到樹中兩點的路徑是唯一的,既然要從1到達n,先考慮最短路徑能否到達
之后剩余的時間作為背包容量,因為其余的點都是非必須的,所以去了必須還要回來,故每條非必要的邊花費都需要*2
所以做法就是先把總時間減去1到n的路徑距離,並把路徑上的花費置零
然后對於每一個結點做背包,對於u的兒子v枚舉一個花費k,注意邊的花費要double

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 105; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[N<<1]; int tot; int first[N],dp[N][N*5],pre[N],id[N],w[N],inq[N],dis[N]; void init(){ tot=0; mems(first,-1); mems(dp,0); mems(inq,0); mems(dis,INF); } void addedge(int u,int v,int W){ edge[tot]=node(v,first[u],W); first[u]=tot++; edge[tot]=node(u,first[v],W); first[v]=tot++; } int n,m,cnt; queue<int>q; void bfs(int s){ while(!q.empty()) q.pop(); pre[s]=1;dis[s]=0; inq[s]=1; q.push(s); while(!q.empty()){ int u=q.front();q.pop();inq[u]=0; if(u==n) break; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(!inq[v]&&dis[u]+edge[i].w<dis[v]){ dis[v]=dis[u]+edge[i].w; q.push(v); inq[u]=1; pre[v]=u; id[v]=i; } } } cnt=0; int u=n; while(u!=1){ cnt+=edge[id[u]].w; edge[id[u]].w=edge[id[u]^1].w=0; u=pre[u]; } } void dfs2(int u,int fa){ for(int i=0;i<=m;i++) dp[u][i]=w[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs2(v,u); for(int j=m;j>=0;j--) for(int k=0;k<=j;k++) if(j-edge[i].w*2-k>=0) dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-edge[i].w*2-k]); } } int u,v,W; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ init(); for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&W); addedge(u,v,W); } for(int i=1;i<=n;i++) scanf("%d",&w[i]); bfs(1); if(cnt>m){ printf("Human beings die in pursuit of wealth, and birds die in pursuit of food!\n"); continue; } m-=cnt; dfs2(1,-1); /*for(int i=1;i<=n;i++){ cout<<i<<':'<<endl; for(int j=0;j<=m;j++) cout<<dp[i][j]<<' ';cout<<endl; }*/ printf("%d\n",dp[1][m]); } return 0; }
14.HDU 3586
題意:給一個限制m,切斷的路徑權值和不超過m,單個邊權值也不超過k,求最小的k使得所有葉子和根不相連
二分一個k
對於一個確定的k,dp[u]表示u的葉子全部和u分離需要的最小花費
考慮葉子節點:dp[u]=INF(不合法狀態
考慮非葉子結點:一開始是沒有和兒子相連,所以dp[u]=0
考慮u的一個兒子v,若(u,v)這條邊是<=lim,則可以選擇在消除這條邊或者是在v的子樹中消除,兩者取一個最優解
最后判斷dp[1]是否小於給定的m

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1005; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[N<<1]; int tot,n,m; int first[N],dp[N]; void init(){ tot=0; mems(first,-1); } void addedge(int u,int v,int w){ edge[tot]=node(v,first[u],w); first[u]=tot++; edge[tot]=node(u,first[v],w); first[v]=tot++; } void dfs(int u,int fa,int lim){ dp[u]=0;int f=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs(v,u,lim);f=1; int tmp; if(edge[i].w<=lim) tmp=edge[i].w; else tmp=INF; dp[u]+=min(tmp,dp[v]); } if(!f) dp[u]=INF; } bool check(int mid){ mems(dp,INF); dfs(1,-1,mid); return dp[1]<=m; } int u,v,w; int main(){ //freopen("in.txt","r",stdin); while(scanf("%d%d",&n,&m)&&(n||m)){ init(); for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); } int low=1,high=INF,mid,ans=-1; while(low<=high){ mid=(low+high)>>1; if(check(mid)){ ans=mid; high=mid-1; } else low=mid+1; } printf("%d\n",ans); } return 0; }
15. POJ 3107
題意:輸出樹中的結點k,刪去k后產生的最大的聯通塊最小
直接兩次dp就好了,第一次處理子樹第二次考慮父親除去當前結點產生的最大塊,維護個最大值

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 50005; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b):e(a),next(b){} }edge[N<<1]; int tot,n,m; int first[N],dp[N],sz[N]; vector<int> ans; void init(){ tot=0; ans.clear(); mems(first,-1); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } void dfs1(int u,int fa){ sz[u]=1;dp[u]=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); sz[u]+=sz[v]; dp[u]=max(dp[u],sz[v]); } } void dfs2(int u,int fa){ for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dp[v]=max(dp[v],n-sz[v]); dfs2(v,u); } } int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d",&n)){ init(); int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } dfs1(1,-1); dfs2(1,-1); int cnt=INF; for(int i=1;i<=n;i++) cnt=min(cnt,dp[i]); //for(int i=1;i<=n;i++) cout<<i<<' '<<dp[i]<<endl; for(int i=1;i<=n;i++) if(dp[i]==cnt) ans.push_back(i); for(int i=0;i<ans.size();i++){ if(i) printf(" "); printf("%d",ans[i]); } puts(""); } return 0; }
16.POJ 3140
題意:選擇一條樹邊斷開,使得分成的兩部分的總點權差最小,輸出最小值
就直接預處理每一個點及其子樹的總點權
枚舉一個點和其父親斷開,取個最優值就好了

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e5+5; const int M = N<<1; const LL MOD = 998244353; const LL INF = 1e18; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[N<<1]; int tot,n,m; int first[N]; LL sz[N],num[N],cnt; void init(){ tot=0;cnt=0; mems(first,-1); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } void dfs(int u,int fa){ sz[u]=num[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs(v,u); sz[u]+=sz[v]; } } int u,v,cas=1; int main(){ //freopen("in.txt","r",stdin); while(scanf("%d%d",&n,&m)&&(n||m)){ init(); for(int i=1;i<=n;i++) scanf("%lld",&num[i]),cnt+=num[i]; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } dfs(1,-1); LL ans=INF; for(int i=2;i<=n;i++){ LL tmp=cnt-sz[i]*2; if(tmp<0) tmp*=-1; ans=min(ans,tmp); } printf("Case %d: %lld\n",cas++,ans); } return 0; }
17. POJ 2486
題意:從樹根1走K步能獲得的最大點權,可以走回頭路
dp[u][i][0/1]表示從u出發走i步,最終回到u/不回到u的最優值
考慮一個葉子結點:dp[u][0][0]=w[u];
考慮一個非葉子結點u及其一個兒子v,枚舉一個k表示對於這個兒子v走的步數
有3種結果:留在v子樹,回到u,留在u其它子樹
畫圖就能看出三種情況的轉移:
留在v子樹:狀態分裂為dp[u][j-k-1][0],dp[v][k][1] 花費為1步
回到u:狀態分裂為dp[u][j-k-2][0],dp[v][k][0] 花費為2步
留在其他子樹:狀態分裂為dp[u][j-k-2][1],dp[v][k][0] 花費為2步

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 105; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b):e(a),next(b){} }edge[N<<1]; int tot; int first[N],dp[N][N<<1][2],w[N]; void init(){ tot=0; mems(first,-1); mems(dp,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } int n,m; void dfs(int u,int fa){ for(int i=0;i<=m;i++) dp[u][i][0]=dp[u][i][1]=w[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs(v,u); for(int j=m;j>=1;j--) for(int k=0;k<=j;k++){ if(j>=k+2) dp[u][j][0]=max(dp[u][j][0],dp[v][j-k-2][0]+dp[u][k][0]); if(j>=k+1) dp[u][j][1]=max(dp[u][j][1],dp[v][j-k-1][1]+dp[u][k][0]); if(j>=k+2) dp[u][j][1]=max(dp[u][j][1],dp[v][j-k-2][0]+dp[u][k][1]); } } } int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ init(); for(int i=1;i<=n;i++) scanf("%d",&w[i]); int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } dfs(1,-1); printf("%d\n",max(dp[1][m][0],dp[1][m][1])); } return 0; }
18. HDU 4044
題意:每個點有ki種選擇,每種選擇對應一種一種花費一種收益 問擁有m元,令x為1到所有葉子(不含1)的路徑的點權和最小值
求x的最大值
一開始的想法是的定義dp[u][i][0/1]為u子樹花i元,u選或者不選的答案
但是后面會發現這樣定義狀態的話對於dp[u][i][1]的合並不是很好處理,因為我沒有記錄u選擇的是哪個方案
看了題解后學了一個新姿勢
定義dp[u][i]為u不選的時候花i元的最優解
預處理一個w[u][i]表示u結點花i元最多能獲得的權值
考慮一個葉子結點:dp[u][i]=w[u][i]
考慮一個非葉子結點不選時候:每一個兒子v加進來的時候,狀態都分裂為dp[v][k]和dp[u][j-k]
對於每一個枚舉的k來說,答案是min(dp[v][k],dp[u][j-k]) 維護這個答案的最大值就是dp[u][j]
考慮完u不選的情況后,再對u單獨做一次背包,枚舉u的花費,取個最優值就是答案

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e3+5; const int M = 205; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[N<<1]; int tot; int first[N],dp[N][M],kind[N],chose[N][M]; void init(){ tot=0; mems(first,-1); mems(dp,INF); mems(chose,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; edge[tot]=node(u,first[v]); first[v]=tot++; } int T,n,m; void dfs1(int u,int fa){ int f=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); f++; for(int j=m;j>=0;j--){ int tmp=0; for(int k=0;k<=j;k++) tmp=max(tmp,min(dp[u][j-k],dp[v][k])); dp[u][j]=tmp; } } if(!f){ for(int i=0;i<=m;i++) dp[u][i]=chose[u][i]; return; } for(int j=m;j>=0;j--) for(int k=0;k<=j;k++) dp[u][j]=max(dp[u][j],dp[u][j-k]+chose[u][k]); } int main(){ //freopen("in.txt","r",stdin); scanf("%d",&T); while(T--){ init(); scanf("%d",&n); int u,v,k; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); } scanf("%d",&m); for(int i=1;i<=n;i++){ scanf("%d",&k); for(int j=1;j<=k;j++){ scanf("%d%d",&u,&v); chose[i][u]=max(chose[i][u],v); } for(int j=1;j<=m;j++) chose[i][j]=max(chose[i][j],chose[i][j-1]); } dfs1(1,-1); printf("%d\n",dp[1][m]); } return 0; }
19.HDU 5758
題意:點有點權邊有花費,點權只能獲得一次,花費每次經過都要扣除,可以走回頭路,問能拿到的最大價值
參考Apple Tree可以知道需要加一維[0/1]表示是否回到u
定義dp[u][0/1]表示從u出發不回/回到u的最優解
求一個結點對於整棵樹的信息一般都是先處理子樹內的信息,再第二次dfs處理父親對答案的影響
先考慮子樹內:
考慮一個葉子結點:dp[u][0]=dp[u][1]=w[u]
考慮一個非葉子u和他的一個兒子v:
對於dp[u][0]來說,v的貢獻只能是dp[v][0]-2*w[u,v],如果這個值小於0我必然不走v
對於dp[u][1]來說,如果不停在v內,v的貢獻也是dp[v][0]-2*w[u,v],如果停在u則狀態分裂為dp[u][0]+dp[v][1]-w[u,v]
再考慮fa對u的影響
考慮fa對u的影響時一般需要把u對fa的影響先排除
對於dp[fa][0]來說u的影響只能是max(0,dp[u][0]-2*w[fa,u]),直接減去就行了
對於dp[fa][1]來說有兩種情況
若最終不停在u,則和dp[fa][0]一樣處理
若停在u,則需要對fa再做一次排除u后的背包
這里將fa對u的影響作為參數下傳,為的是保證在推u的時候fa的值是對於整棵樹的,而其余結點是對於其子樹的
之所以這樣做是因為用fa更新u的時候,排除u對fa的影響之后fa相當於u的一棵新子樹

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e5+50; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next,w; node(){} node(int a,int b,int c):e(a),next(b),w(c){} }edge[N<<1]; int tot,n,m; int first[N],dp[N][2],w[N],id[N]; void init(){ tot=0; for(int i=1;i<=n;i++) first[i]=-1; } void addedge(int u,int v,int W){ edge[tot]=node(v,first[u],W); first[u]=tot++; edge[tot]=node(u,first[v],W); first[v]=tot++; } void dfs1(int u,int fa){ dp[u][0]=dp[u][1]=w[u]; id[u]=-1; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs1(v,u); int tmp=max(0,dp[v][0]-2*edge[i].w); dp[u][1]+=tmp; if(dp[u][1]<dp[u][0]+max(0,dp[v][1]-edge[i].w)){ dp[u][1]=dp[u][0]+max(0,dp[v][1]-edge[i].w); id[u]=v; } dp[u][0]+=tmp; } } int ans[N]; void dfs2(int u,int fa,int f0,int f1){ int t[2]={dp[u][0],dp[u][1]}; int idd=id[u]; t[1]+=f0; if(t[1]<t[0]+f1){ t[1]=t[0]+f1; idd=fa; } t[0]+=f0; ans[u]=max(t[0],t[1]); for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; if(v==idd){ int tmp0=w[u]+f0,tmp1=w[u]+f1; for(int k=first[u];k!=-1;k=edge[k].next){ int vv=edge[k].e; if(vv==fa||vv==v) continue; int tmp=max(0,dp[vv][0]-2*edge[k].w); tmp1+=tmp; tmp1=max(tmp1,tmp0+max(0,dp[vv][1]-edge[k].w)); tmp0+=tmp; } tmp0=max(0,tmp0-2*edge[i].w); tmp1=max(0,tmp1-edge[i].w); dfs2(v,u,tmp0,tmp1); } else{ int tmp=max(0,dp[v][0]-2*edge[i].w); int tmp0=max(0,t[0]-tmp-2*edge[i].w); int tmp1=max(0,t[1]-tmp-edge[i].w); dfs2(v,u,tmp0,tmp1); } } } int T,u,v,W,cas=1; int main(){ //freopen("in.txt","r",stdin); //freopen("pending.txt","w",stdout); scanf("%d",&T); while(T--){ scanf("%d",&n); init(); for(int i=1;i<=n;i++) scanf("%d",&w[i]); for(int i=1;i<n;i++){ scanf("%d%d%d",&u,&v,&W); addedge(u,v,W); } dfs1(1,-1); dfs2(1,-1,0,0); printf("Case #%d:\n",cas++); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); } return 0; }
20.NUBT 1638
題意:建圖略麻煩,抽象出來就是說樹中選m個結點,其中rt到每一個葉子的路徑上被選的結點數不超過k,問獲得的最大權值是多少
定義dp[u][i][j][0/1]表示u子樹選i個,最多的路徑選了j個,u選/不選
dp值全部初始化為-1表示不合法狀態
考慮一個葉子結點:選的話是dp[u][1][1][1]=w[u] 不選dp[u][0][0][0]=0
考慮一個非葉子結點u和他的一個兒子v:
對於狀態dp[u][i][j][0],可能轉移過來的狀態有dp[u][i-k][j][0]+dp[v][k][0~j][0/1]或者dp[u][i-k][0~j][0]+dp[v][k][j][0/1]
對於狀態dp[u][i][j][1],有dp[u][i-k][j][1]+dp[v][k][0~j-1][0/1]或dp[u][i-k][0~j][1]+dp[v][k][j-1][0/1](之所以v是j-1為上限是因為u選了的話合並后v的最長鏈長度必然+1,這樣做是為了限制最長鏈在j范圍內)

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 120; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct Point{ int x,y; }p[N<<2]; bool cmp(Point a,Point b){ return a.x<b.x; } struct Node{ int y,l,r; }point[N]; bool cmp1(Node a,Node b){ if(a.y==b.y) return a.l<b.l; return a.y>b.y; } struct node{ int e,next; }edge[N<<1]; int first[N],cnt,tot; int dp[N][N][11][2],w[N],sz[N]; void init(){ tot=0;cnt=0; mems(first,-1); mems(dp,-1); } void addedge(int u,int v){ edge[tot]=(node){v,first[u]}; first[u]=tot++; } void build(int l,int r,int fa){ for(int i=l+1;i<=r;i++){ if(p[l].y==p[i].y){ addedge(fa,++cnt); w[cnt]=p[i].x-p[l].x; //cout<<fa<<' '<<cnt<<' '<<w[cnt]<<endl; build(l+1,i-1,cnt); build(i,r,fa); return ; } else if(p[i].y>p[l].y){ build(l+1,i-1,fa); build(i,r,fa); } } } int n,m,K; void dfs(int u){ dp[u][1][1][1]=w[u]; dp[u][0][0][0]=0; sz[u]=1; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; dfs(v); sz[u]+=sz[v]; for(int j=sz[u];j>=0;j--) for(int k=0;k<=K;k++) for(int p=0;p<=min(sz[v],j);p++) for(int kk=0;kk<=k;kk++){ if(dp[u][j-p][k][0]!=-1&&(dp[v][p][kk][0]!=-1||dp[v][p][kk][1]!=-1)) dp[u][j][k][0]=max(dp[u][j][k][0],dp[u][j-p][k][0]+max(dp[v][p][kk][0],dp[v][p][kk][1])); if(dp[u][j-p][kk][0]!=-1&&(dp[v][p][k][0]!=-1||dp[v][p][k][1]!=-1)) dp[u][j][k][0]=max(dp[u][j][k][0],dp[u][j-p][kk][0]+max(dp[v][p][k][0],dp[v][p][k][1])); if(kk>=1&&dp[u][j-p][k][1]!=-1&&(dp[v][p][kk-1][0]!=-1||dp[v][p][kk-1][1]!=-1)) dp[u][j][k][1]=max(dp[u][j][k][1],dp[u][j-p][k][1]+max(dp[v][p][kk-1][0],dp[v][p][kk-1][1])); if(k>=1&&dp[u][j-p][kk][1]!=-1&&(dp[v][p][k-1][0]!=-1||dp[v][p][k-1][1]!=-1)) dp[u][j][k][1]=max(dp[u][j][k][1],dp[u][j-p][kk][1]+max(dp[v][p][k-1][0],dp[v][p][k-1][1])); } } } int cas=1; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d%d",&n,&m,&K)){ init(); for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y); sort(p+1,p+1+n,cmp); build(1,n,0); int rt=0; dfs(rt); int ans=-1; for(int i=0;i<K;i++) ans=max(ans,dp[rt][m][i][0]); printf("Case %d: %d\n",cas++,ans); } return 0; }
21.HDU 5758
題意:邊花費1,可以從一個點瞬移到另一個點,問瞬移次數最少的前提下遍歷所有邊的最小花費
可以知道瞬移次數是(葉子數+1)/2
定義dp[u][0]為遍歷完u后的最小花費(只是第一次dp,答案不一定是最終答案
考慮非葉子u和他的一個兒子v:
若v有偶數個兒子,則要在瞬移次數最少的情況下遍歷(u,v)這條邊,就必須有兩個結點和v外的結點配對,所以(u,v)對答案貢獻2
同理奇數的時候貢獻1
如果總葉子數是偶數那答案已經出來了
但是如果總葉子數是奇數這樣的答案可能會 偏大
其實奇數的情況就是在偶數的情況加了一條沒有分叉的單鏈
所以再定義dp[u][1]為遍歷u后的最小花費(第二次dp,獨立與第一次dp
對於一個u,枚舉這條單鏈出現的兒子v:
如果這個兒子就是一條單鏈,並且u是這條單鏈的起點,則dp[u][1]=min(dp[u][1],dp[u][0])
如果不是兒子v不是單鏈則把這條單鏈從v中剔除,則v中的葉子數的奇偶就變化了
dp[u][1]先減去dp[v][0],再減去奇偶變化的影響d,再加上單鏈存在與v的dp值dp[v][1],維護最優解
最后若總葉子數是偶數則取第一次dp的結果,否則取第二次的

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e5+5; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next; }edge[N<<1]; int first[N],cnt,tot; LL dp[N][2]; int sz[N],deg[N]; void init(){ tot=0; mems(first,-1); mems(deg,0); mems(dp,INF); } void addedge(int u,int v){ edge[tot]=(node){v,first[u]}; first[u]=tot++; edge[tot]=(node){u,first[v]}; first[v]=tot++; } void dfs(int u,int fa){ dp[u][0]=0;sz[u]=0; int flag=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; flag++; dfs(v,u); sz[u]+=sz[v]; dp[u][0]+=dp[v][0]; if(sz[v]&1) dp[u][0]++; else dp[u][0]+=2; } for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; if(flag>1&&sz[v]==1) dp[u][1]=min(dp[u][1],dp[u][0]); if(dp[v][1]==INF) continue; LL tmp=dp[u][0]-dp[v][0]+dp[v][1]; if(sz[v]&1) tmp++; else tmp--; dp[u][1]=min(dp[u][1],tmp); } if(!flag) sz[u]=1; } int n,m,u,v,T; int main(){ //freopen("in.txt","r",stdin); scanf("%d",&T); while(T--){ init(); scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); deg[v]++;deg[u]++; } int rt=1,cnt=0; for(int i=1;i<=n;i++) { if(deg[i]!=1) rt=i; else cnt++; } dfs(rt,-1); printf("%lld\n",dp[rt][cnt&1]); } return 0; }
22.codeforces Round #322(Div.2) F
題意:把葉子平分染成兩種顏色,其余點隨便染,求最少有對相鄰的點顏色不同 輸入保證葉子是偶數個
定義dp[u][i][0/1]表示u子樹染i個兒子0色,u染0/1色
這題經典的地方在於葉子和非葉子的初始情況不一樣
先dp值全部賦INF表示不合法
對於葉子來說:dp[u][1][0]=0,dp[u][0][1]=0,其余兩個狀態不合法
考慮非葉子u:
非葉子u的顏色對第二維是沒影響的,所以dp[u][0][1]=dp[u][0][0]=0
由於這里是必須要選擇兒子的情況,之前有些題目是兒子可以不選
必須選的情況的處理方法有兩種,第一種是先強制放一種狀態,再枚舉其他狀態看看是否更優,有就替換,沒有就保留原來的
或者說是用一個變量去存放兒子的最優情況,然后把這個最優情況強制合並到原狀態里
這里我采用的是第二種方法

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 5005; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int e,next; }edge[N<<1]; int first[N],tot; int dp[N][N][2],deg[N],leaf[N]; void init(){ tot=0; mems(first,-1); mems(dp,INF); mems(deg,0); } void addedge(int u,int v){ edge[tot]=(node){v,first[u]}; first[u]=tot++; edge[tot]=(node){u,first[v]}; first[v]=tot++; } int n,u,v; void dfs(int u,int fa){ dp[u][0][1]=0; dp[u][0][0]=0; leaf[u]=0; int flag=0; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs(v,u); flag=1; leaf[u]+=leaf[v]; for(int j=leaf[u];j>=0;j--){ int miv[2]={INF,INF}; for(int k=0;k<=min(leaf[v],j);k++){ miv[0]=min(miv[0],min(dp[u][j-k][0]+dp[v][k][1]+1,dp[u][j-k][0]+dp[v][k][0])); miv[1]=min(miv[1],min(dp[u][j-k][1]+dp[v][k][1],dp[u][j-k][1]+dp[v][k][0]+1)); } dp[u][j][0]=miv[0]; dp[u][j][1]=miv[1]; } } if(!flag){ leaf[u]=1; dp[u][0][0]=INF; dp[u][1][0]=0; } } int main(){ init(); scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); addedge(u,v); deg[v]++;deg[u]++; } if(n==2) return 0*puts("1"); int rt,cnt=0; for(int i=1;i<=n;i++){ if(deg[i]!=1) rt=i; else cnt++; } dfs(rt,-1); //for(int i=1;i<=n;i++) //for(int j=0;j<=leaf[i];j++) printf("i_%d j_%d %d %d\n",i,j,dp[i][j][0],dp[i][j][1]); printf("%d\n",min(dp[rt][cnt/2][0],dp[rt][cnt/2][1])); return 0; }
23.COJ 1793
題意:對於每個點給一個限制xi表示xi選了才能選i,現在最多選m個人,問在這些限制下最多能選幾個
如果連邊(xi->i)一眼看過去結構很像樹,因為每個結點只有一個父親
但是這里可能會構成強聯通分量
不過很容易想到把強聯通分量縮點后也是可以構成樹或者森林的
森林我們可以建立一個虛根0來合並成一棵樹
一開始有個地方沒想明白,就是對於一個強聯通分量來說可能去掉一個點后還是強聯通分量,所以一個強聯通分量內我選擇幾個人是不好判斷的
但是仔細想想題目給的條件,每個點的入度只能為1,而這樣構成的強聯通分量只能是一個簡單環
而對於一個簡單環來說我只能全取或者全不取
所以問題就轉化為了一個m的背包,每個點的sz即使花費也是價值,問能拿到的最大價值是多少
這里有個地方需要變通
這里是選了父親才能選擇兒子,所以對於一個結點u以及他的兒子v來說,我要把v加進來我首先得保證u被選
所以我枚舉v的容量k的時候始終保證j-k也就是剩余的容量始終是大於sz[u]的,這樣就能保證u始終被選

#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 1e3+5; const int M = N<<1; const LL MOD = 998244353; const int INF = 0x3f3f3f3f; struct node{ int s,e,next; }edge[M]; int tot,id; int first[N]; int block,tp,n,m; int low[N],dfn[N],ins[N],deg[N]; int st[N],belong[N],sz[N]; int dp[N][N]; void init(int n){ tot=0;tp=0;block=0;id=0; for(int i=0;i<=n;i++){ first[i]=-1; dfn[i]=0; ins[i]=0; deg[i]=0; for(int j=0;j<=m;j++) dp[i][j]=0; } } void addedge(int u,int v){ edge[tot]=(node){u,v,first[u]}; first[u]=tot++; //edge[tot]=(node){u,first[v]}; //first[v]=tot++; } void tarjan(int u){ low[u]=dfn[u]=++id; ins[u]=1; st[++tp]=u; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(ins[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]){ block++; sz[block]=0; int v; do{ v=st[tp--]; ins[v]=0; belong[v]=block; sz[block]++; } while(v!=u); } } void rebuild(){ int tot2=tot; tot=0; for(int i=0;i<=n;i++) first[i]=-1; for(int i=0;i<tot2;i++){ int u=belong[edge[i].s]; int v=belong[edge[i].e]; if(u==v) continue; deg[v]++; addedge(u,v); } } void dfs(int u,int fa){ for(int i=sz[u];i<=m;i++) dp[u][i]=sz[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; if(v==fa) continue; dfs(v,u); for(int j=m;j>=sz[u];j--) for(int k=0;k<=j-sz[u];k++) dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k]); } } int u; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m)){ init(n); for(int i=1;i<=n;i++){ scanf("%d",&u); addedge(u,i); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); rebuild(); int rt=0;sz[0]=0; for(int i=1;i<=block;i++) if(!deg[i]) addedge(rt,i); dfs(rt,-1); printf("%d\n",dp[rt][m]==-1?0:dp[rt][m]); } return 0; }