[BZOJ3354]创世纪(基环树+树形DP)

[BZOJ3354]创世纪(基环树+树形DP)

题面

applepi手里有一本书《创世纪》,里面记录了这样一个故事……

上帝手中有着N 种被称作“世界元素”的东西,现在他要把它们中的一部分投放到一个新的空间中去以建造世界。每种世界元素都可以限制另外一种世界元素,所以说上帝希望所有被投放的世界元素都有至少一个没有被投放的世界元素能够限制它,这样上帝就可以保持对世界的控制。

由于那个著名的有关于上帝能不能制造一块连自己都不能举起的大石头的二律背反命题,我们知道上帝不是万能的,而且不但不是万能的,他甚至有事情需要找你帮忙——上帝希望知道他最多可以投放多少种世界元素,但是他只会O(2^N) 级别的算法。虽然上帝拥有无限多的时间,但是他也是个急性子。你需要帮助上帝解决这个问题

分析

\(x\)能控制\(y\),就连有向边\((y,x)\)控制关系构成一棵基环树森林.考虑在环上任选两点\(s,t\),其中\(t\)\(s\)控制,将环断开,就可以转化成树形DP

\(dp_{x,0/1}\)表示\(x\)不选/选,子树(指外向树)内最多可以选择的点的个数。那么对于非\(s,t\)的点来说,有:

\[dp_{x,0}=\sum_{y \in son(x)} \max(dp_{y,0},dp_{y,1}) \]

\[dp_{x,1}=\max_{y \in son(x)}(dp_{x,0}-\max(dp_{y,0},dp_{y,1})+dp_{y,0}+1) \]

这是因为\(x\)不选,则能够控制它的点可随便选。否则必须找一个能够控制它的点不选。

\(t\)除了树上的点外还可以被\(s\)控制,那么如果\(s\)不选,\(t\)在断环后的树里是否被未选择的点控制都没关系,此时\(dp_{t,1}=dp_{t,0}+1\). 如果\(s\)选,那么那么\(t,s\)的边没有用,能控制\(s\)的只有树上的点,直接按照一般点DP即可。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define maxn 1000000
#define INF 0x3f3f3f3f
using namespace std;
template<typename T>void qread(T &x){
	x=0;
	T sign=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') sign=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	x=x*sign;
}

int n;
int a[maxn+5];
vector<int>E[maxn+5];
bool vis[maxn+5];
void find_root(int x,int &s,int &t){
	//找环要自底向上搜,因此用原图. 
	if(vis[x]){
		s=x;
		t=a[x]; 
	}
	else{
		vis[x]=1;
		find_root(a[x],s,t);
	}
} 
int dp[maxn+5][2];//x不选/选 
void dfs(int x,int s,int t){
	vis[x]=1;//顺便把子树标记了,否则循环的时候会重复 
	dp[x][0]=dp[x][1]=0;
	for(int i=0;i<(int)E[x].size();i++){
		int y=E[x][i];
		if(y!=s){
			dfs(y,s,t);
			dp[x][0]+=max(dp[y][0],dp[y][1]);//如果dp[x][0]不选,可以控制x的点怎么选都可以 
		}
	}
	if(x==t){
		dp[x][1]=dp[x][0]+1;//t被s控制,那么如果s不选,t在断环后的树里怎么选都没关系 
	}else{//对于一般的点来说,如果x选,可以控制x的点至少有一个不选 
		for(int i=0;i<(int)E[x].size();i++){
			int y=E[x][i];
			if(y!=s) dp[x][1]=max(dp[x][1],dp[x][0]-max(dp[y][0],dp[y][1])+dp[y][0]+1);//去掉y 
		}
	} 
}
int main(){
//	freopen("5.in","r",stdin);
	qread(n);
	for(int i=1;i<=n;i++){
		qread(a[i]);
		E[a[i]].push_back(i);//建反图 
	}
	int ans=0;
	for(int i=1,s,t,cur;i<=n;i++){
		if(!vis[i]){
			s=t=0,cur=0;
			find_root(i,s,t);
			dfs(s,s,t);//如果s不选,t处dp要特判
			cur=max(cur,dp[s][0]); 
			dfs(s,s,0);//如果s选,那么t,s的边没有用,能控制s的只有树上的点
			cur=max(cur,dp[s][1]);
			ans+=cur;
//			printf("%d %d\n",cur,s);
		}
	}
	printf("%d\n",ans);
}
posted @ 2020-12-01 15:26  birchtree  阅读(103)  评论(0编辑  收藏  举报