BZOJ1040[ZJOI2008]骑士

 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <iostream>
 5 #include <algorithm>
 6 using namespace std;
 7 
 8 typedef long long ll;
 9 const int N=1000005,M=2000005;
10 
11 int n,tot,U,V,E,now[N],prep[M],son[M],val[N];
12 ll ans,f[N],g[N];
13 bool vis[N];
14 
15 int read(){
16     static int x,f; static char ch; x=0,f=1;
17     for (ch=getchar();!isdigit(ch);ch=getchar()) if (ch=='-') f=-1;
18     for (;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x*f;
19 }
20 
21 void link(int x,int y){prep[++tot]=now[x],now[x]=tot,son[tot]=y;}
22 
23 void dfs(int x,int y){
24     vis[x]=1;
25     for (int i=now[x],so=son[i];i!=-1;i=prep[i],so=son[i]){
26         if (so!=y){
27             if (vis[so]){
28                 U=x,V=so,E=i;
29                 continue;
30             }else dfs(so,x);
31         }
32     }
33 }
34 
35 void tree_dp(int x,int y,int ban){
36     f[x]=val[x],g[x]=0;
37     for (int i=now[x],so=son[i];i!=-1;i=prep[i],so=son[i]){
38         if (so!=y&&i!=ban&&(i^1)!=ban){
39             tree_dp(so,x,ban);
40             f[x]+=g[so],g[x]+=max(f[so],g[so]);
41         }
42     }
43 }
44 
45 int main(){
46     n=read(); tot=-1;
47     memset(now,-1,sizeof(now));
48     for (int x,i=1;i<=n;i++){
49         val[i]=read(),x=read();
50         link(x,i),link(i,x);
51     }
52     ans=0;
53     for (int i=1;i<=n;i++){
54         if (!vis[i]){
55             dfs(i,0);
56             tree_dp(U,0,E);
57             ll x=g[U];
58             tree_dp(V,0,E);
59             ans+=max(x,g[V]);
60         }
61     }
62     printf("%lld\n",ans);    
63     return 0;
64 }
View Code

题目大意:给定一个基环外向森林,点带权,求最大点独立集。n<=10^6.

做法:基环树dp,设(u,v)为环上的一条边,f[x]表示以x为根的子树中选x的最大权和,g[x]不选。

考虑如下步骤:

1:将这条边断开,变为树。

2:u,v必有一个点不选,考虑分别以u,v为根做一次dp,然后用max(g[u],g[v])更新答案。

 

细节:(u,v)怎么找? dfs一次,如果x的出边son之前访问过了,那么(x,son)就是换上的一条边,记录一下即可。

posted @ 2017-06-26 18:19 oyzx~ 阅读(...) 评论(...) 编辑 收藏