树形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\n",ans); 56 } 57 return 0; 58 }
二、树的最大独立集
给一棵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 }
三、树形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 }
四、树上次长路径
可以考虑到在一棵树上,会有一条或多条直径,所以只需要判断在当前树上,是否存在一条且仅有一条直径。
如果有,那么次长路径为直径-1,相反,路径就是直径。
需要三遍Bfs或Dfs,最后一遍是验证。