dp专题六–树形dp
HDU 1502–树的最大独立集
Problem Description
Input
Output
Sample Input
Sample Output
题目要求
树的结构为人际关系 雇员和老班只能要求一个人 转换成树的最大独立集问题
参考AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| using namespace std; int dp[maxn][2]; int vis[maxn]; vector<int>e[maxn]; void dfs(int root) { vis[root]=1; for(int i=0;i<e[root].size();i++) { if(!vis[e[root][i]]) { dfs(e[root][i]); dp[root][0]+=max(dp[e[root][i]][1],dp[e[root][i]][0]); dp[root][1]+=dp[e[root][i]][0]; } } } //状态转移方程: //用dp[i][0]表示不选中i号,周围的都可以选的最大值,dp[i][1]表示选中i号,那么周围都不能选的最大值。 //dp[i][0]+=(dp[j][1],dp[j][0]) //dp[i][1]+=dp[j][0] int main() { /*freopen("input.txt","r",stdin);*/ int n; while(scanf("%d",&n)!=EOF) { /*if(n==0) break;*/ memset(vis,0,sizeof(vis)); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { scanf("%d",&dp[i][1]); e[i].clear(); } for(int i=1;i<=n;i++) { int u,v; scanf("%d%d",&u,&v); if(u==0&&v==0) break; e[u].push_back(v); e[v].push_back(u); } dfs(1); printf("%d\n",max(dp[1][1],dp[1][0])); //第一个点取与不取两种情况取最大值 } return 0; }
|
HDU 4118–树形dp
Problem Description
Input
Output
Sample Input
Sample Output
题目要求
树的结构:点代表城市 权值为距离
开始时每个城市都有一个人 接着每个人都要移动 最后每个城市依然要保持1个人 求所有人移动的最大距离
参考AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| using namespace std; struct node { int to,w; node(int to,int w):to(to),w(w){} }; vector<node>e[maxn]; int vis[maxn]; LL ans=0; //注意答案开为LL型 int n; int num[maxn]; //num记录该结点下的结点数 void dfs(int u) { vis[u]=1; num[u]=1; //每次访问一个结点时初始化为1 for(int i=0;i<e[u].size();i++) { int next=e[u][i].to; if(!vis[next]) { dfs(next); num[u]+=num[next]; //父节点的下结点数等于所有子结点数之和 LL minnum=min(num[next],n-num[next]); //找出这个结点两边 结点小的那一边的结点数 ans+=e[u][i].w*minnum*2LL; //该结点数×权值×2就是答案 } } } //因为结点两边只有最小的那一边移动才能满足两边顺利移动 //例如一边结点数为a 另一边为b 假设a<b 那么选取a b那一端也一定能找出a个点与之匹配 //ans需要乘以2是因为来回要走两次 int main() { /*freopen("input.txt","r",stdin);*/ int t; cin>>t; int flag=1; while(t--) { cin>>n; for(int i=0;i<=n;i++) e[i].clear(); for(int i=1;i<n;i++) { int u,v,w; cin>>u>>v>>w; e[u].push_back(node(v,w)); e[v].push_back(node(u,w)); } ans=0; memset(num, 0, sizeof(num)); memset(vis, 0, sizeof(vis)); dfs(1); cout<<"Case #"<<flag++<<": "<<ans<<endl; } return 0; }
|
POJ 1655–树的重心
Problem Description
Input
Output
Sample Input
Sample Output
题目要求
求出一棵树的重心和最大子树的最小结点数
参考AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| using namespace std; vector<int>e[maxn]; int vis[maxn]; int son[maxn]; int ans,size; int n; void dfs(int cur) { vis[cur]=1; son[cur]=1; //son用来计算每个结点的子结点数 int temp=0; for(int i=0;i<e[cur].size();i++) { int next=e[cur][i]; if(!vis[next]) { dfs(next); son[cur]+=son[next]; //深搜一个 该结点的子结点数+1 temp=max(temp,son[next]); //首先算出深搜下去的结点最大值 } } temp=max(temp,n-son[cur]); //此段代码需放在for循环外 表示搜到头后 逆向回头判断另一边是否更大 if(temp<size||(temp==size&&cur<ans)) //找到最大子树后比较后其出结点最小值 { //若结点数相同返回位置小的 ans=cur; size=temp; } } int main() { /*freopen("input.txt","r",stdin);*/ int t; cin>>t; while(t--) { cin>>n; for(int i=0;i<=n;i++) e[i].clear(); for(int i=1;i<n;i++) { int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } ans=INT_MAX,size=INT_MAX; //初始化为最大值 因为要求最小值 memset(vis,0,sizeof(vis)); dfs(1); cout<<ans<<" "<<size<<endl; } return 0; }
|
POJ 3107–树的重心
Problem Description
Input
Output
Sample Input
Sample Output
题目要去
求出树的所有重心后排序输出
参考AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| using namespace std; struct Edge //此题卡时间 必须用前向星存图 使用vector会超时 { int to,next; }; Edge edge[2*maxn]; //无向图开两倍 int head[maxn]; int vis[maxn]; int son[maxn]; int n,ans[maxn],size,num,cnt; void add(int u,int v) { edge[cnt].to = v; edge[cnt].next = head[u]; head[u] = cnt++; } void init() { memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); size=INT_MAX; num=0; cnt=0; } void dfs(int cur) { vis[cur]=1; son[cur]=1; int temp=0; for(int i=head[cur];i!=-1;i=edge[i].next) { int next=edge[i].to; if(!vis[next]) { dfs(next); son[cur]+=son[next]; temp=max(temp,son[next]); } } temp=max(temp,n-son[cur]); if(temp<size) //与上题一样 区别在于用ans数组才存储重心 { num=1; //只要有比当前小的 ans和num均初始化 ans[0]=cur; size=temp; } else if(temp==size) //否则存入数组 ans[num++]=cur; } int main() { /*freopen("input.txt","r",stdin);*/ while(scanf("%d",&n)!=EOF) { init(); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs(1); sort(ans,ans+num); for(int i=0;i<num;i++) printf("%d ",ans[i]); puts(""); } return 0; }
|
hihocoder 1050–树的直径
Input
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第一行为一个整数N,意义如前文所述。
每组测试数据的第2~N行,每行分别描述一根木棍,其中第i+1行为两个整数Ai,Bi,表示第i根木棍连接的两个小球的编号。
对于20%的数据,满足N<=10。
对于50%的数据,满足N<=10^3。
对于100%的数据,满足N<=10^5,1<=Ai<=N, 1<=Bi<=N
小Hi的Tip:那些用数组存储树边的记得要开两倍大小哦!
Output
对于每组测试数据,输出一个整数Ans,表示给出的这棵树中距离最远的两个结点之间相隔的距离。
Sample Input
8
1 2
1 3
1 4
4 5
3 6
6 7
7 8
Sample Output
6
参考AC代码(一):两次dfs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| //此题用了一个性质:随便找一个点 dfs搜索到最远位置 该点一定是直径中的一个点 //接着对该点再进行dfs搜索 得到的就是最大直径 using namespace std; vector<int>e[maxn]; int vis[maxn]; int n,p,ans; void dfs(int deep,int root) { if(deep>ans) //取最深长度 { ans=deep; p=root; //p记录第一次走的最远位置 } vis[root]=1; for(int i=0;i<e[root].size();i++) { int next=e[root][i]; if(!vis[next]) dfs(deep+1,next); } } int main() { /*freopen("input.txt","r",stdin);*/ while(scanf("%d",&n)!=EOF) { for(int i=0;i<=n;i++) e[i].clear(); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); e[u].push_back(v); e[v].push_back(u); } p=0,ans=0; memset(vis,0,sizeof(vis)); dfs(0,1); ans=0; //第二次深搜注意初始化 memset(vis,0,sizeof(vis)); dfs(0,p); printf("%d\n",ans); } return 0; }
|
参考AC代码(二):使用fi+se后序遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| using namespace std; vector<int>e[maxn]; int vis[maxn]; int n,ans; int dfs(int root) { int fi=0,se=0,d; vis[root]=1; for(int i=0;i<e[root].size();i++) { int next=e[root][i]; if(!vis[next]) { d=dfs(next)+1; //dfs返回的结果表示以next为根节点的子树的最长路径值 if(d>fi) //1加的是子树的根节点到next的长度 { se=fi; //d比fi大 都要更新 fi=d; } else if(d>se) //只比se大 更新se即可 se=d; } } ans=max(ans,fi+se); return fi; } int main() { /*freopen("input.txt","r",stdin);*/ while(cin>>n) { for(int i=0;i<=n;i++) e[i].clear(); for(int i=1;i<n;i++) { int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } memset(vis,0,sizeof(vis)); ans=0; dfs(1); cout<<ans<<endl; } return 0; }
|
HDU 2196–给定权求树的直径
Problem Description
Input
Output
Sample Input
Sample Output
题目要求
给定树的边权 对于树中的每个点 求出离该点的最远距离
参考AC代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| using namespace std; struct node { int to,w; node(int to,int w):to(to),w(w){} }; vector<node>e[maxn]; int n; int dp[maxn][3]; //dp[i][0]表示以i为顶点的最大值 dp[i][1]表示以i为顶点的次大值 void dfs1(int root) //dp[i][2]表示i的父节点部分获得的最大值 { int fi=0,se=0; int temp=0; for(int i=0;i<e[root].size();i++) { int next=e[root][i].to; dfs1(next); temp=dp[next][0]+e[root][i].w; if(temp>fi) { se=fi; fi=temp; } else if(temp>se) se=temp; } dp[root][0]=fi; //与求树的一般直径相同 求出fi与se的值 dp[root][1]=se; } void dfs2(int root) { for(int i=0;i<e[root].size();i++) { int next=e[root][i].to; //下式是dp[i][2]的状态转移方程 详情看解析 dp[next][2]=max(dp[root][2],dp[next][0]+e[root][i].w==dp[root][0]?dp[root][1]:dp[root][0])+e[root][i].w; dfs2(next); } } int main() { /*freopen("input.txt","r",stdin);*/ while(cin>>n) { for(int i=0;i<=n;i++) e[i].clear(); for(int i=2;i<=n;i++) { int p,w; cin>>p>>w; e[p].push_back(node(i,w)); } dfs1(1); dp[1][2]=0; //根没有父节点 dfs2(1); for(int i=1;i<=n;i++) cout<<max(dp[i][0],dp[i][2])<<endl; } return 0; }
|
思路
对于上面那棵树,要求距结点2的最长距离,那么,就需要知道以2为顶点的子树(蓝色圈起的部分,我们叫它Tree(2)),距顶点2的最远距离L1
还有知道2的父节点1为根节点的树Tree(1)-Tree(2)部分(即红色圈起部分),距离结点1的最长距离+dist(1,2) = L2,那么最终距离结点2最远的距离就是
max{L1,L2}
f[i][0],表示顶点为i的子树的,距顶点i的最长距离
f[i][1],表示顶点为i的子树的,距顶点i的次长距离
f[i][2],表示Tree(i的父节点)-Tree(i)的最长距离+i跟i的父节点距离
要求所有的f[i][0]和f[i][1]很简单,只要先做一次dfs求每个结点到叶子结点的最长距离和次长距离即可。
然后要求f[i][2], 可以从父节点递推到子节点,
假设节点u有n个子节点,分别是v1,v2…vn
那么
如果vi不是u最长距离经过的节点,f[vi][2] = dist(vi,u)+max(f[u][0], f[u][2])
如果vi是u最长距离经过的节点,那么不能选择f[u][0],因为这保存的就是最长距离,要选择Tree(u)第二大距离secondDist,
可得f[vi][2] = dist(vi, u) + max(f[u][1], f[u][2])