『一本通』树形DP

传送门:《信息学奥赛一本通》提高版题解索引


 

周年纪念晚会(没有上司的舞会)

题面

 1 #include<bits/stdc++.h>
 2 #define N 6005
 3 using namespace std;
 4 int n,root,hp[N],f[N][2];
 5 bool vis[N];
 6 vector<int> son[N];
 7 inline int read() {
 8     int x=0,f=1; char c=getchar();
 9     while(c<'0'||c>'9') {if(c=='-')f=-1; c=getchar();}
10     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
11     return x*f;
12 }
13 
14 void dp(int x) {
15     f[x][1]=hp[x];
16     for(int i=0;i<son[x].size();i++) {
17         int y=son[x][i];
18         dp(y);
19         f[x][0]+=max(f[y][1],f[y][0]);
20         f[x][1]+=f[y][0];
21     }
22 }
23 
24 int main() {
25     memset(vis,0,sizeof(vis));
26     n=read();
27     for(int i=1;i<=n;i++) hp[i]=read();
28     for(int i=1;i<n;i++) {
29         int l=read(),k=read();
30         son[k].push_back(l); vis[l]=1;
31     }
32     for(int i=1;i<=n;i++)
33      if(!vis[i]) {root=i; break;}
34     dp(root);
35     printf("%d",max(f[root][1],f[root][0]));
36     return 0;
37 }
38 /* 
39 树形DP入门题 
40 设f[i][0]表示第i个人不去所能获得的最大值
41 f[i][1]表示第i个人去所能获得的最大值
42 方程如题意所示。
43 f[i][0]=sum(max(f[son][1],f[son][0])); 下属去不去随意 
44 f[i][1]=hp[i]+sum(f[son][0]); 加上快乐值,下属只能不去 
45 */

 

选课

题面

 1 #include<bits/stdc++.h>
 2 #define N 1005
 3 using namespace std;
 4 int n,m,f[N][N],fro[N],cnt;
 5 struct edge{int to,nxt;}e[N];
 6 void add(int x,int y) {
 7     e[++cnt].to=y; e[cnt].nxt=fro[x]; fro[x]=cnt;
 8 }
 9 
10 void dp(int x) {
11     for(int i=fro[x];i!=0;i=e[i].nxt) {
12         int to=e[i].to;
13         dp(to);
14         for(int j=m+1;j>=1;j--)
15          for(int k=0;k<j;k++)
16           f[x][j]=max(f[x][j],f[to][k]+f[x][j-k]);
17     }
18 }
19 
20 int main() {
21     scanf("%d%d",&n,&m);
22     for(int i=1;i<=n;i++) {
23         int fa; scanf("%d",&fa);
24         add(fa,i); 
25         scanf("%d",&f[i][1]);
26     }
27     dp(0);
28     printf("%d",f[0][m+1]);
29     return 0;
30 }
31 /*
32 树形DP 
33 f[i][j]表示以i为根节点选j门课的最优值。
34 01背包的思想优化空间。
35 把0号节点看做根节点,列入必选的范围,即要选m+1门课。
36 (森林---->树) 
37 */ 

 

二叉苹果树

题面

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,q,cnt,f[105][105],fro[105],sum[105];
 4 struct edge{int to,v,nxt;}e[205];
 5 inline int read() {
 6     int x=0,f=1; char c=getchar();
 7     while(c<'0'||c>'9') {if(c=='-')f=-1; c=getchar();}
 8     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
 9     return x*f;
10 }
11 void add(int x,int y,int z) {
12     e[++cnt].to=y,e[cnt].v=z,e[cnt].nxt=fro[x]; fro[x]=cnt;
13 }
14 
15 void dp(int x,int fa) {
16     for(int i=fro[x];i;i=e[i].nxt) {
17         int y=e[i].to;
18         if(y==fa) continue;
19         dp(y,x); 
20         sum[x]+=sum[y]+1; //统计目前深搜过的边数 
21         for(int j=sum[x];j>0;j--) //01背包的思想优化空间
22          for(int k=0;k<j;k++)
23           f[x][j]=max(f[x][j],f[x][j-k-1]+f[y][k]+e[i].v); //注意边的细节 
24     }
25 }
26 
27 int main() {
28     n=read(),q=read();
29     for(int i=1;i<n;i++) {
30         int x=read(),y=read(),z=read();
31         add(x,y,z); add(y,x,z); 
32     }
33     dp(1,0);
34     printf("%d",f[1][q]);
35 }
36 /*
37 树形DP
38 思路类似【选课】(见上) 
39 */

 

数字转换

题面

 1 #include<bits/stdc++.h>
 2 #define N 50005
 3 using namespace std;
 4 int n,sum[N],d1[N],d2[N];
 5 
 6 int main() {
 7     scanf("%d",&n);
 8     for(int i=1;i<=n;i++) //预处理约数和 
 9      for(int j=2;j<=n/i;j++) sum[i*j]+=i;
10     for(int i=n;i>=1;i--) //i的父亲(sum[i])一定比i小,所以n~1枚举i 
11      if(sum[i]<i) 
12       if(d1[i]+1>d1[sum[i]]) {
13           d2[sum[i]]=d1[sum[i]];
14           d1[sum[i]]=d1[i]+1;
15       }
16       else if(d1[i]+1>d2[sum[i]]) d2[sum[i]]=d1[i]+1;
17     int ans=0;
18     for(int i=1;i<=n;i++) ans=max(ans,d1[i]+d2[i]);
19     printf("%d",ans);
20 }
21 /*
22 树形DP
23 预处理出小于等于N的每个数的约数和sum[i]。
24 如果sum[i]<i,那么i和sum[i]可以互相转化。
25 即两点间连一条边,i为sum[i]的一个儿子。
26 最后的结果是一棵树,问题就转化为求这棵树的直径。
27 */

 

旅游规划

题面

 1 #include<bits/stdc++.h>
 2 #define N 200003
 3 using namespace std;
 4 int fro[N],cnt,n,ans=0,f[N],s[N]; //f[i]是以i为根的最长链,s[i]是次长链 
 5 struct node{int to,nxt;}a[N<<1];
 6 inline int read() {
 7     int x=0,f=1; char c=getchar();
 8     while(c<'0'||c>'9') {if(c=='-')f=-1; c=getchar();}
 9     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
10     return x*f;
11 }
12 void add(int x,int y) {
13     a[++cnt].to=y; a[cnt].nxt=fro[x]; fro[x]=cnt;
14 }
15 
16 int dfs1(int u,int fa) { //寻找子树中的最长链和次长链
17     for(int i=fro[u];i!=0;i=a[i].nxt) {
18         int to=a[i].to;
19         if(to==fa) continue;
20         dfs1(to,u);
21         if(f[to]+1>f[u]) s[u]=f[u],f[u]=f[to]+1; //更新最长链(把原来最长链的值赋给次长链)
22         else if(f[to]+1>s[u]) s[u]=f[to]+1; //更新次长链
23     }
24 }
25 
26 void dfs2(int u,int fa,int dis) { //搜索u上面的最长链(用dis表示),更新f[u]和s[u]
27 /*
28 因为f[i]和s[i]的值由子树转移
29 所以就忽略了上面的最长链
30 */
31     for(int i=fro[u];i!=0;i=a[i].nxt) {
32         int to=a[i].to;
33         if(to==fa) continue;
34         if(f[to]+1==f[u]) dfs2(to,u,max(dis+1,s[u]+1)); //如果to已经在u的子树最长链上
35         else dfs2(to,u,max(dis+1,f[u]+1));
36     }
37     if(dis>f[u]) s[u]=f[u],f[u]=dis; //更新
38     else if(dis>s[u]) s[u]=dis;
39 }
40 
41 int main() {
42     n=read();
43     for(int i=1;i<n;i++) {
44         int x=read()+1,y=read()+1;
45         add(x,y); add(y,x);
46     }
47     dfs1(1,1);
48     dfs2(1,1,0);
49     for(int i=1;i<=n;i++) ans=max(ans,f[i]+s[i]); //因为最长链和次长链从不同位置转移,所以树上最长链为两个的和
50     for(int i=1;i<=n;i++) 
51      if(f[i]+s[i]==ans) printf("%d\n",i-1);
52     return 0;
53 }
54 //树形DP

 

骑士

题面

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define N 1000001
 4 using namespace std;
 5 int n,cnt,fa[N];
 6 ll ans,fight[N],fro[N],f[N][2];
 7 bool use[N];
 8 struct edge{int to,nxt;}e[N];
 9 inline int read() {
10     int x=0,f=1; char c=getchar();
11     while(c<'0'||c>'9') {if(c=='-')f=-1; c=getchar();}
12     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
13     return x*f;
14 }
15 ll mmax(ll x,ll y) {return x>y?x:y;}
16 void add(int x,int y) {
17     e[++cnt].to=y; e[cnt].nxt=fro[x]; fro[x]=cnt;
18 }
19 
20 void dfs(int x,int r) {
21     use[x]=1;
22     f[x][1]=fight[x];
23     f[x][0]=0;
24     for(int i=fro[x];i!=0;i=e[i].nxt) {
25         int to=e[i].to;
26         if(to!=r) {
27             dfs(to,r);
28             f[x][1]+=f[to][0];
29             f[x][0]+=mmax(f[to][0],f[to][1]);
30         }
31         else f[to][1]=-100000000;
32     }
33 }
34 
35 void find(int x) {
36     while(!use[x]) use[x]=1,x=fa[x];
37     dfs(x,x);
38     ll hh=f[x][0];
39     x=fa[x];
40     dfs(x,x);
41     ans+=mmax(hh,f[x][0]);
42 }
43 
44 int main() {
45     n=read();
46     for(int i=1;i<=n;i++) {
47         scanf("%lld",&fight[i]); int y=read();
48         add(y,i); fa[i]=y;
49     }
50     for(int i=1;i<=n;i++)
51      if(!use[i]) find(i);
52     printf("%lld",ans);
53     return 0;
54 }
55 /*
56 树形DP
57 【没有上司的舞会】加难版。有多个联通块,每个联通块都是一个基环树。
58 把每个联通块的环上删一条边,删掉的边所连接的两点 x y,不能同时选。
59 所以我们分别强制 x y 其中一个点不选,对新树深搜。
60 状态转移方程同【没有上司的舞会】(见上)
61 */

 

皇宫看守

题面

 1 #include<bits/stdc++.h>
 2 #define N 1505
 3 #define INF 0x3f3f3f3f
 4 using namespace std;
 5 int fro[N],cnt,n,f[N][3];
 6 struct node{int to,nxt;}a[N<<1];
 7 inline int read() {
 8     int x=0,f=1; char c=getchar();
 9     while(c<'0'||c>'9') {if(c=='-')f=-1; c=getchar();}
10     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
11     return x*f;
12 }
13 void add(int x,int y) {
14     a[++cnt].to=y; a[cnt].nxt=fro[x]; fro[x]=cnt;
15 }
16 
17 void dfs(int u,int fa) {
18     int hh=INF;
19     for(int i=fro[u];i!=0;i=a[i].nxt) {
20         int y=a[i].to;
21         if(y!=fa) {
22             dfs(y,u);
23             f[u][0]+=min(f[y][1],f[y][2]);
24             f[u][1]+=min(f[y][1],f[y][2]);
25             hh=min(hh,f[y][2]-min(f[y][1],f[y][2]));
26             f[u][2]+=min(f[y][0],min(f[y][1],f[y][2]));
27         }
28     }    
29     f[u][1]+=hh;
30 }
31 
32 int main() {
33     n=read();
34     for(int i=1;i<=n;i++) {
35         int x=read(); f[x][2]=read();
36         int m=read();
37         for(int j=1;j<=m;j++) {
38             int y=read(); 
39             add(x,y); add(y,x);
40         }
41     }
42     dfs(1,0);
43     printf("%d",min(f[1][1],f[1][2]));
44     return 0;
45 }
46 /*
47 树形DP
48 f[i][0]:父亲守卫自己(儿子们只要不让自己守护就行)
49 f[i][1]:儿子守卫自己(儿子们不让自己守护,并强制一个儿子自己守护自己)
50 f[i][2]:自己守卫自己(儿子们怎么样都行)
51 */

 

战略游戏

题面

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,cnt,fro[1505],f[1505][2];
 4 struct edge{int to,nxt;}e[3005];
 5 inline int read() {
 6     int x=0; char c=getchar();
 7     while(c<'0'||c>'9') c=getchar();
 8     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
 9     return x;
10 }
11 void add(int x,int y) {
12     e[++cnt].to=y,e[cnt].nxt=fro[x]; fro[x]=cnt;
13 }
14 
15 void dfs(int x,int fa) {
16     f[x][1]=1;
17     for(int i=fro[x];i;i=e[i].nxt) {
18         int y=e[i].to;
19         if(y==fa) continue;
20         dfs(y,x);
21         f[x][1]+=min(f[y][0],f[y][1]);
22         f[x][0]+=f[y][1];
23     }
24 }
25 
26 int main() {
27     n=read();
28     for(int i=1;i<=n;i++) {
29         int x=read()+1,k=read();
30         for(int j=1;j<=k;j++) {
31             int y=read()+1;
32             add(x,y); add(y,x);
33         }
34     }
35     dfs(1,0);
36     printf("%d",min(f[1][1],f[1][0]));
37 }
38 /*
39 树形DP
40 f[x][0]:在x节点不放置士兵的最小值 
41 f[x][1]:在x节点放置士兵的最小值 
42 */ 

 

加分二叉树

题面

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,f[40][40],root[40][40];
 4 inline int read() {
 5     int x=0; char c=getchar();
 6     while(c<'0'||c>'9') c=getchar();
 7     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
 8     return x;
 9 }
10 
11 void print(int l,int r) { //递归输出前序遍历 
12     if(l>r) return ;
13     printf("%d ",root[l][r]);
14     print(l,root[l][r]-1);
15     print(root[l][r]+1,r);
16 }
17 
18 int main() {
19     n=read();
20     for(int i=1;i<=n;i++) {
21         f[i][i]=read(),root[i][i]=i;
22         f[i][i-1]=1; //空子树加分为1 
23     }
24     for(int i=2;i<=n;i++)
25      for(int l=1;l+i-1<=n;l++) {
26         int r=l+i-1;
27         for(int k=l;k<=r;k++) //枚举根的位置 
28          if(f[l][r]<f[l][k-1]*f[k+1][r]+f[k][k]) {
29             f[l][r]=f[l][k-1]*f[k+1][r]+f[k][k];
30             root[l][r]=k; //记录根的位置 
31          }
32      }
33     printf("%d\n",f[1][n]);
34     print(1,n);
35 } 
36 //乱入的区间DP
37 //f[i][j]:节点i到节点j为树的最大加分

 

叶子的染色

题面

 1 #include<bits/stdc++.h>
 2 #define N 10005
 3 #define INF 0x3f3f3f3f
 4 using namespace std;
 5 int n,m,cnt,fro[N],du[N],c[N],f[N][N];
 6 struct edge{int to,nxt;}e[N<<1];
 7 inline int read() {
 8     int x=0; char c=getchar();
 9     while(c<'0'||c>'9') c=getchar();
10     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
11     return x;
12 }
13 void add(int x,int y) {
14     e[++cnt].to=y,e[cnt].nxt=fro[x]; fro[x]=cnt;
15 }
16 
17 void dp(int x,int fa) {
18     if(du[x]==1&&fa) { //叶子节点只能染色为c[x] 
19         f[x][c[x]]=1,f[x][c[x]^1]=INF;
20         return;
21     }
22     f[x][1]=f[x][0]=1;
23     for(int i=fro[x];i;i=e[i].nxt) {
24         int y=e[i].to;
25         if(y==fa) continue;
26         dp(y,x);
27         f[x][1]+=min(f[y][1]-1,f[y][0]); //x与y染相同颜色就取消y的染色 
28         f[x][0]+=min(f[y][1],f[y][0]-1);
29     }
30 }
31 
32 int main() {
33     m=read(),n=read();
34     for(int i=1;i<=n;i++) c[i]=read();
35     for(int i=1;i<m;i++) {
36         int x=read(),y=read();
37         add(x,y); add(y,x); 
38         du[x]++,du[y]++;
39     }
40     dp(n+1,0);
41     printf("%d",min(f[n+1][1],f[n+1][0]));
42 }
43 /*
44 树形DP
45 f[x][0]表示x节点着以黑色的最小值
46 f[x][1]表示x节点着以白色的最小值
47 */ 

 

完结  ≧▽≦ (2018.12.31)

posted @ 2018-12-08 16:39  YeLingqi  阅读(224)  评论(0编辑  收藏  举报