[ZJOI]骑士

一开始在loj上看到,不知道难度,一看,这不是没有上司的舞会吗......

然后样例没过,哦,这不是棵树,那么怎样成为一棵树?

删一条边,枚举复杂度会超,代码也没调出来,就看了题解......

作者: HullEssien 更新时间: 2017-09-25 19:56  在Ta的博客查看  14 


本题解比较啰嗦

尽力用最容易明白的语言来讲

希望对大家有帮助

基环树

一个听起来很高大上的名词

但是不要被这个名字吓跑了

其实这个题

思路很简单

没比一道普通的树形DP难多少

(依然不懂这个题为什么省选/NOI-,明明和树形DP入门题没有上司的舞会那么像)

只是细节比较多而已

(其实也不算很多啦)

认真读完题目我们会发现

因为一个骑士有且只有一个最讨厌的人

而且这个骑士不会讨厌自己

即该图中是没有自环的

然后 从网上搜的很多题解都是用无向图存边

然后用一些高超的技巧(如位运算)判断卡掉无向二元环

但是题解中的kczno1

这位神犇就没有使用无向图

事实证明存无向图是完全没有必要的

因为本身有向图就携带着指向上一个节点的信息

而且这个信息更利于维护我们删边之后的操作

这样 不仅省去了判断的麻烦

还有利于维护信息

何乐而不为?

所以 考虑这个有向图

我们把x所讨厌的人y设为x的父亲节点

这样考虑每一个人都有且只有一条出边

所以对一个"联通块"

只有根节点有机会形成环

即环一定包含根节点

为什么呢?

因为一个点的出度只能为1

考虑非根节点

它的出度一定是贡献给它的父亲的

而根节点它的出度只能贡献给它的后代

(这里的"根节点""叶子节点"都只是为了描述方便,并不严谨,也许可以理解为"删边以后的叶子和根"?)

所以我们又解决了一个问题:

每个联通块内有且只有一个简单环

这样 我们考虑把每个联通块的环上删一条边

这样它必然构成树

然后要注意

删掉的边所连接的两点x,y

是不能同时选的

所以我们分别强制x,y其中一个点不选

对新树跑DP

显然相邻的点是不能选的

所以得到状态转移方程:

用f[i][0/1]表示以i为根节点的子树选i/不选i所能获得的最大价值

则 f[i][0]=sigema(max(f[son][0],f[son][1])); for each son of i

f[i][1]=sigema(f[son][0]); for each son of i

应该就很清楚了

再一个细节就是

答案会爆int

我交了数遍

都卡在这里

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=1e6+7;
 7 int val[maxn],head[maxn],n,fa[maxn],ss,num;
 8 ll ans,f[maxn][3];
 9 bool vis[maxn];
10 struct Edge{
11     int next;int to;
12 }edge[maxn];
13 int read(){
14     int f=1;int x=0;char s=getchar();
15     while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
16     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
17     x*=f;
18     return x;
19 }
20 void add(int from,int to){
21     edge[++num].next=head[from];
22     edge[num].to=to;
23     head[from]=num;
24 }
25 int dfs(int x){
26     vis[x]=true;
27     f[x][0]=0;f[x][1]=val[x];
28     for(int i=head[x];i;i=edge[i].next){
29         int v=edge[i].to;
30         if(v!=ss){
31             dfs(v);
32             f[x][1]+=f[v][0];
33             f[x][0]+=max(f[v][0],f[v][1]);
34         }     
35         else{
36             f[v][1]=-2147483647;
37         }    
38     }
39 }
40 void find(int x){
41     vis[x]=true;
42     while(vis[fa[x]]==false){
43         x=fa[x];
44         vis[x]=true;
45     }
46     ss=x;
47     dfs(ss);
48     ll an=max(f[ss][0],f[ss][1]);
49     vis[ss]=true;
50     ss=fa[ss];
51     dfs(ss);
52     ans+=max(an,max(f[ss][0],f[ss][1]));
53 }
54 int main(){
55     n=read();
56     for(int i=1;i<=n;i++){
57         val[i]=read();fa[i]=read();
58         add(fa[i],i);
59     }
60     for(int i=1;i<=n;i++){
61         if(!vis[i]) find(i);
62     }
63     cout<<ans<<endl;
64     return 0; 
65 }

还有就是树形DP写的太复杂了

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn=1e6+7;
 7 int fa[maxn],num,n,head[maxn],val[maxn];
 8 ll f[maxn][7],ans;
 9 bool vis[maxn];
10 struct Edge{
11     int next;int to;
12 }edge[maxn];
13 void add(int from,int to){
14     edge[++num].next=head[from];
15     edge[num].to=to;
16     head[from]=num;
17 }
18 int dfs(int x,int opt){
19     if(head[x]==0){
20         if(opt==0||vis[x]==true) return 0;
21         else return val[x];
22     }
23     ll ans1=0;
24     if(opt==1){
25         for(int i=head[x];i;i=edge[i].next){
26             int v=edge[i].to;
27             ans1+=dfs(v,0);
28         }
29         ans1+=val[x];
30     }
31     if(opt==0){
32         for(int i=head[x];i;i=edge[i].next){
33             int v=edge[i].to;
34             ans1+=max(dfs(v,0),dfs(v,1));
35         }
36     }
37     return ans1;
38 }
39 int main(){
40     cin>>n;
41     for(int i=1;i<=n;i++){
42         cin>>val[i]>>fa[i];
43         add(fa[i],i);
44     }
45     for(int i=1;i<=n;i++){
46         vis[i]=true;
47         int aa=head[i];head[i]=0;
48         ll t=dfs(fa[i],1);
49         vis[i]=false;
50         t+=dfs(fa[i],0);
51         ans=max(ans,t);
52         head[i]=aa;
53     }
54     cout<<ans<<endl;
55     return 0; 
56 }
原文地址:https://www.cnblogs.com/lcan/p/9574911.html