【题意分析】

  给你一个带权基环树森林,求它的点集的无邻点子集的最大权值和。

【解题思路】

  对于树的部分,做一遍拓扑排序+递推即可(f[i][j]表示第i个节点选取状态为j(0/1)可以得到的最大权值和)。时间复杂度O(n)。

  对于环的部分,做一遍DP。g[i][j]表示环上第i个节点选取状态为j时,编号从1到i的节点的最大子树权值和。转移方程:

•g[i][0]=max(g[i-1][0],g[i-1][1])+f[i][0];

•g[i][1]=g[i-1][0]+f[i][1];

  但因为这是个环,所以要枚举环上第一个元素取不取。若取,则答案为g[n][0],否则答案为max(g[n][0],g[n][1])。时间复杂度O(n)。

  总时间复杂度O(n)。

【参考代码】

 1 #include <bits/stdc++.h>
 2 #define range(i,low,high) for(register int i=(low);i<(high);++i)
 3 #define dange(i,high,low) for(register int i=(high);i>(low);--i)
 4 #define __function__(type) __attribute__((optimize("-O2"))) inline type
 5 #define __procedure__      __attribute__((optimize("-O2"))) inline void
 6 using namespace std;
 7  
 8 static int n; bool vis[1000005]={0};
 9 int oue[1000005],ind[1000005],que[1000005],loop[1000005];
10 long long v[1000005],f[1000005][2]={0},g[1000005][2];
11  
12 int main()
13 {
14     scanf("%d",&n);
15     range(i,1,n+1) scanf("%lld%d",v+i,oue+i),++ind[oue[i]];
16     int tail=0; long long ans=0;
17     range(i,1,n+1) {f[i][1]=v[i]; if(!ind[i]) que[tail++]=i;}
18     range(head,0,tail)
19     {
20         int fr=que[head],to=oue[fr]; vis[to]=1;
21         f[to][0]+=max(f[fr][0],f[fr][1]),f[to][1]+=f[fr][0];
22         if(!--ind[to]) que[tail++]=to;
23     }
24     range(i,1,n+1) if(ind[i]>0)
25     {
26         int cnt=1; --ind[loop[1]=i];
27         for(int j=oue[i];j!=i;j=oue[j]) --ind[loop[++cnt]=j];
28         g[1][0]=f[loop[1]][0],g[1][1]=0;
29         range(j,2,cnt+1)
30         {
31             int k=loop[j];
32             g[j][0]=max(g[j-1][0],g[j-1][1])+f[k][0];
33             g[j][1]=g[j-1][0]+f[k][1];
34         }
35         long long tmp=max(g[cnt][0],g[cnt][1]);
36         g[1][0]=0,g[1][1]=f[loop[1]][1];
37         range(j,2,cnt+1)
38         {
39             int k=loop[j];
40             g[j][0]=max(g[j-1][0],g[j-1][1])+f[k][0];
41             g[j][1]=g[j-1][0]+f[k][1];
42         }
43         ans+=max(tmp,g[cnt][0]);
44     }
45     return printf("%lld\n",ans),0;
46 }
View Code