新利国际网站新利国际网站

18新利luck
新利国际网址

【DP】树形DP

 

树形DP

在树上发生的状态转移,一般来说,状态第一维是以i为根的子树。

在Dfs的回溯部分状态转移。

 

一、树的直径

在树上随便找一点w,以w为根作Dfs或Bfs,保存距离w结点u的编号及其距离。

再以那个节点u为根,作Dfs或Bfs,找到距离u最远的结点v。

此时,u-v为树的一条直径。

 

POJ1985 Cow Marathon

这道题给出N个点,M-1条边(字符是没有用的),然后求出直径。

 

1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<queue> 6 using namespace std; 7 #define MAX_N 40010 8 struct Edge{ 9 int to,next,value;10 }edge[MAX_N*2];11 int head[MAX_N];12 int N,M,cnt,ans,node;13 void Add(int u,int v,int w){14 edge[++cnt].to=v;15 edge[cnt].value=w;16 edge[cnt].next=head[u];17 head[u]=cnt;18 }19 void Bfs(int u){20 queue<int>Q;21 bool vis[MAX_N];memset(vis,false,sizeof(vis));22 int dis[MAX_N];memset(dis,0,sizeof(dis));23 Q.push(u);24 vis[u]=true;25 ans=0,node=u;26 while(!Q.empty()){27 int x=Q.front();Q.pop();28 for(int i=head[x];~i;i=edge[i].next){29 int v=edge[i].to,w=edge[i].value;30 if(!vis[v]&&dis[v]<dis[x]+w){31 dis[v]=dis[x]+w;32 vis[v]=true;33 if(ans<dis[v]){34 ans=dis[v];35 node=v;36 }37 Q.push(v);38 }39 }40 }41 }42 int main(){43 while(~scanf("%d%d",&N,&M)){44 memset(head,-1,sizeof(head));45 memset(edge,0,sizeof(edge));46 char str[5];47 cnt=0;48 for(int i=1;i<=M;i++){49 int u,v,w;scanf("%d%d%d%s",&u,&v,&w,str);50 Add(u,v,w);51 Add(v,u,w);52 }53 Bfs(1);54 Bfs(node);55 printf("%d",ans);56 }57 return 0;58 }Bfs

 

 

 

 二、树的最大独立集

给一棵N个结点的子树,要求选出尽量多的点,使它们两两不相邻。

用树形DP的思路:状态dp(i,j)表示以i为根的子树,i结点是否被选取。

对于这颗树,dp(i,0)代表不选i这个结点,可以得出dp(i,0)=sum(max(dp(k,0),dp(k,1)))。

可以写出代码。

 

Luogu1352没有上司的舞会

这道题需要记录每个v结点的入度,入度为0的结点就是根节点。

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 #define MAX_N 6010 6 struct Edge{ 7 int to,next; 8 }edge[MAX_N*2]; 9 int head[MAX_N],R[MAX_N],dp[MAX_N][2],_index[MAX_N];10 int N,cnt;11 void Add(int u,int v){12 edge[++cnt].to=v;13 edge[cnt].next=head[u];14 head[u]=cnt;15 }16 void Dfs(int u){17 dp[u][0]=0;18 dp[u][1]=R[u];19 for(int i=head[u];~i;i=edge[i].next){20 int v=edge[i].to;21 Dfs(v);22 dp[u][0]+=max(dp[v][0],dp[v][1]);23 dp[u][1]+=dp[v][0];24 }25 }26 int main(){27 memset(head,-1,sizeof(head));28 scanf("%d",&N);29 for(int i=1;i<=N;i++)scanf("%d",&R[i]);30 for(int i=1;i<N;i++){31 int u,v;scanf("%d%d",&v,&u);32 Add(u,v);33 _index[v]++;34 }35 int root=0;36 for(int i=1;i<=N;i++){37 if(!_index[i]){38 root=i;39 break;40 }41 }42 Dfs(root);43 int Max=-1;44 for(int i=1;i<=N;i++){45 Max=max(Max,max(dp[i][0],dp[i][1])); 46 }47 printf("%d",Max);48 return 0;49 }Luogu1352

 

三、树形dp+01背包

只是在Dfs的回溯过程中加入了01背包的实现。

Luogu2014选课

值得注意的是,会有很多个根节点,所以把0号结点默认为根节点,所以此时的M要加1,因为0号结点是必选的。

 

1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 #define MAX_N 310 6 struct Edge{ 7 int to,next; 8 }edge[MAX_N*2]; 9 int cost[MAX_N],dp[MAX_N][MAX_N],head[MAX_N];10 int N,M,cnt;11 void Add(int u,int v){12 edge[++cnt].to=v;13 edge[cnt].next=head[u];14 head[u]=cnt;15 } 16 void Dfs(int u){17 dp[u][1]=cost[u];18 for(int i=head[u];~i;i=edge[i].next){19 int v=edge[i].to;20 Dfs(v);21 for(int i=M+1;i>=1;i--){22 for(int j=i-1;j>=1;j--){23 dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[v][j]);24 }25 }26 27 } 28 }29 int main(){30 memset(head,-1,sizeof(head));31 scanf("%d%d",&N,&M);32 for(int i=1;i<=N;i++){33 int s,k;scanf("%d%d",&k,&s);34 Add(k,i);35 cost[i]=s;36 }37 Dfs(0);38 printf("%d",dp[0][M+1]);39 return 0;40 }Luou2014

 

 

四、树上次长路径

可以考虑到在一棵树上,会有一条或多条直径,所以只需要判断在当前树上,是否存在一条且仅有一条直径。

如果有,那么次长路径为直径-1,相反,路径就是直径。

需要三遍Bfs或Dfs,最后一遍是验证。

, 1, 0, 9);

欢迎阅读本文章: 梅浩宇

新利国际官方导航

18新利luck