实际上基环树DP的名字是假的。。

这个限制关系可以看成每个点有一条出边,所以就是一个内向基环树森林。

找出每个基环树的环,然后对于树的部分,做DP,设状态选或不选为$f_{x,0/1}$,则

$f_{x,0}=\sum\limits_{y\in son_x} \max\{f_{y,0},f_{y,1}\}$

$f_{x,1}=\sum\limits_{y\in son_x} \max\{f_{y,0},f_{y,1}\}+[全选了f_{y,1}?]\sum\limits_{y\in son_x} \max\{f_{y,0}-f_{y,1}\}$

这个DP很简单,但是在环上很难处理,因为每个点取不取既和环上前一个点有关也和儿子有关,这个环上的点DP转移是有后效性的。

采用基环树DP另一个常用的手段:断环成树,树形容易处理,避免后效性。断环时,应当注意断开的环上两个点相互影响的关系。

比如此题,每一个点可能会影响后一个点的选择,分两种情况:此点不影响下一个点,则断开后,以此点为根做树形DP,那么两点互不影响,题目条件都成立。

若考虑有影响,则需要这个点不选,让下一个点可以随便选儿子的两个状态较大值而不用担心没有指向他的不选的点。

综上,断边后做两次DP即可。

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define mst(x) memset(x,0,sizeof x)
#define dbg(x) cerr << #x << " = " << x <<endl
#define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,):;}
template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,):;}
template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
template<typename T>inline T read(T&x){
x=;int f=;char c;while(!isdigit(c=getchar()))if(c=='-')f=;
while(isdigit(c))x=x*+(c&),c=getchar();return f?x=-x:x;
}
const int N=1e6+;
struct thxorz{
int head[N],nxt[N<<],to[N<<],tot;
thxorz(){tot=;}
inline void add(int x,int y){
to[++tot]=y,nxt[tot]=head[x],head[x]=tot;
to[++tot]=x,nxt[tot]=head[y],head[y]=tot;
}
}G;
int n;
#define y G.to[j]
int vis[N],to,rt,flag,ans,res,ban;
void dfs(int x){//dbg(x);
vis[x]=;
for(register int j=G.head[x];j;j=G.nxt[j])if(!(j&)){
if(vis[y])return rt=x,to=y,ban=j,void();
else dfs(y);
}
}
int f[N][];
void dp1(int x,int fa){
vis[x]=;int chosen=;f[x][]=;
for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^)){
dp1(y,x);
if(f[y][]<f[y][])f[x][]+=f[y][],f[x][]+=f[y][];
else f[x][]+=f[y][],f[x][]+=f[y][],chosen=;
}
if(chosen){
int tmp=-N;
for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^))MAX(tmp,f[y][]-f[y][]);
f[x][]+=tmp;
}//dbg2(x,chosen);
}
void dp2(int x,int fa){
int chosen=;f[x][]=,f[x][]=;
for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^)){
dp2(y,x);
if(f[y][]<f[y][])f[x][]+=f[y][],f[x][]+=f[y][];
else f[x][]+=f[y][],f[x][]+=f[y][],chosen=;
}
if(chosen&&x!=to){
int tmp=-N;
for(register int j=G.head[x];j;j=G.nxt[j])if(y^fa&&j^ban&&j^(ban^))MAX(tmp,f[y][]-f[y][]);
f[x][]+=tmp;
}
}
#undef y int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout);
read(n);
for(register int i=,x;i<=n;++i)read(x),G.add(i,x);
for(register int i=;i<=n;++i,res=)if(!vis[i]){
dfs(i);
dp1(rt,);//不考虑断环之后根对断点有影响
res=_max(f[rt][],f[rt][]);
dp2(rt,);//考虑断环之后根对断点有影响(即不选根)
MAX(res,f[rt][]);
ans+=res;
}
printf("%d\n",ans);
return ;
}

总结:基环树另一种常见做法:断环成树,考虑影响,做两次树形DP。

BZOJ3037 创世纪[基环树DP]的更多相关文章

  1. tyvj 创世纪 - 基环树

    codevs :   传送门 Description 上帝手中有着N 种被称作“世界元素”的东西,现在他要把它们中的一部分投放到一个新的空间中去以建造世界. 每种世界元素都可以限制另外一种世界元素,所 ...

  2. BZOJ3037 创世纪(基环树DP)

    基环树DP,攻的当受的儿子,f表选,g表不选.并查集维护攻受关系.若有环则记录,DP受的后把它当祖宗,再DP攻的. #include <cstdio> #include <iostr ...

  3. bzoj1791[IOI2008]Island岛屿(基环树+DP)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1791 题目大意:给你一棵n条边的基环树森林,要你求出所有基环树/树的直径之和.n< ...

  4. 【bzoj1040】[ZJOI2008]骑士 并查集+基环树dp

    题目描述 Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战火绵延五百里,在 ...

  5. BZOJ1040:骑士(基环树DP)

    Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战火绵延五百里,在和平环境中 ...

  6. 基环树DP

    基环树DP Page1:问题 啥是基环树?就是在一棵树上增加一条边. Page2:基环树的几种情况 无向 有向:基环外向树,基环内向树. Page3:处理问题的基本方式 1.断环成树 2.分别处理树和 ...

  7. [CSP-S模拟测试]:卡常题/b(基环树+DP)

    题目描述 $ρ$有一个二分连通无向图,$X$方点.$Y$方点均为$n$个(编号为$1\sim n$).这个二分图比较特殊,每一个$Y$方点的度为$2$,一条黑色边,一条白色边.所有黑色边权值均为$a$ ...

  8. [ZJOI2008] 骑士 - 基环树dp

    一类基环树dp都是这个套路吧 随便拆掉环上的一条边 然后跑树形dp,设\(f[i][0/1]\)表示以第\(i\)个人为根的子树,第\(i\)个人选或不选,能收获的最大值 以断点\(u,v\)为根分别 ...

  9. [bzoj2878][Noi2012]迷失游乐园(基环树dp)

    [bzoj2878][Noi2012]迷失游乐园(基环树dp) bzoj luogu 题意:一颗数或是基环树,随机从某个点开始一直走,不走已经到过的点,求无路可走时的路径长期望. 对于一棵树: 用两个 ...

随机推荐

  1. Vue.directive()的用法和实例

    官网实例: https://cn.vuejs.org/v2/api/#Vue-directive https://cn.vuejs.org/v2/guide/custom-directive.html ...

  2. tesseract 3.04在centos6上安装

    tesseract是一个开源的OCR文字识别工具 查找相关文章:tesseract   tesseract 4.0一直安装失败,后来参照网上的方法,成功安装3.04 1 2 3 4 5 6 7 8 9 ...

  3. [转]史上最最最详细的手写Promise教程

    我们工作中免不了运用promise用来解决异步回调问题.平时用的很多库或者插件都运用了promise 例如axios.fetch等等.但是你知道promise是咋写出来的呢? 别怕-这里有本promi ...

  4. 初识RedisCluster集群

    为什么需要Redis集群 需要提高更大的并发量 Redis官方提出拥有10万QPS的请求量 如果业务需要Redis拥有100万的QPS 可以通过集群来提升并发量. 需要存储更大的数据量 一般服务器的机 ...

  5. kmp跑两串的最大相同前后缀 HDU2594

    题意:http://acm.hdu.edu.cn/showproblem.php?pid=2594 如题. 思路: Next数组记录的是pos位置失配时要跑到哪里,所以最后得再添加一个字符‘#’. 连 ...

  6. thinkphp5分页查询paginate()传递参数

    使用paginate()分页,我这里实现的是搜索后分页显示,翻页后传递搜索关键字 www.demo.com/home/search/?k=搜索关键字&page=2 搜索分页源码在: think ...

  7. java对象序列化并存储到文件中

    ● 如何将一个Java对象序列化到文件里 使用输入输出流,,一个是ObjectOutputStream 对象,ObjectOutputStream 负责向指定的流中写入序列化的对象.当从文件中读取序列 ...

  8. C语言如何才能使用bool类型

    解决办法:加上头文件#include<stdbool.h> ,就可以直接使用类型bool = true/false; #include<stdio.h> 2 #include& ...

  9. SqlServer学习之存储过程

    前言:对于存储过程一直有一种抵触的心理,因为毕业至今所在的公司开发组都不是很规范,对于开发的一些注意事项并没有很多的规定,只是在知乎上查找相关知识的时候,看到很多人对于在程序里使用存储过程的不好之处都 ...

  10. 【css】display:flex和display:box有什么区别

    说法一: 注意:前者是flex 2012年的语法,也将是以后标准的语法,大部分浏览器已经实现了无前缀版本.后者是2009年的语法,已经过时,是需要加上对应前缀的.所以兼容性的代码,大致如下displa ...