1. 程式人生 > 實用技巧 >P2607 [ZJOI2008]騎士 基環樹,樹dp;

P2607 [ZJOI2008]騎士 基環樹,樹dp;

P2607 [ZJOI2008]騎士

本題本質上就是樹dp,和沒有上司的舞會差不多,只不過多了一個對基環樹的處理。

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define ll long long 
using namespace std;
const int maxn=2e6;
int n,cnt;
ll val[maxn];
int root;
int head[maxn];
int vis[maxn];
ll fa[maxn];
ll f[maxn][2];
ll ans;
int rot;
ll max1(ll x,ll y){
	if(x>=y){
		return x;
	}
	return y;
}
inline ll read(){
	ll ret=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-'){
			f=-f;
		}
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		ret=ret*10+(ch^'0');
		ch=getchar();
	} 
	return f*ret;
}
struct node{
	int nex,to;
}e[maxn];
void add(int u,int v){
	cnt++;
	e[cnt].nex=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}
void dp(int x){
	vis[x]=1;//打標記,用處是表明聯通塊,把該聯通塊進行標記
	//以便遍歷整連線關係,除此之外並無用處 
	f[x][1]=val[x];
	f[x][0]=0;
	for(int i=head[x];i;i=e[i].nex){
		int to=e[i].to;
		if(to!=root){
			dp(to);
			f[x][0]+=max1(f[to][0],f[to][1]);
			f[x][1]+=f[to][0]; 
		}
		else{
			f[to][1]=-maxn;//將環刪邊,定有一點不能選,故定此點不選 
		}
	}
}
void find(int x){
	vis[x]=1;
	root=x;
	while(!vis[fa[root]]){//我們的目的是為了找環,而且該連通塊先前一定未遍歷過
		//故該方法可以找出環來 
		root=fa[root];
		vis[root]=1;
	}
	/*
	該題中圖的特殊性
	導致找環必須要從下到上
	若從上到下
	那麼如果起點為樹邊就無法找到環
	該圖的根為環 
	*/
	dp(root);
	ll t;
	t=max(f[root][1],f[root][0]);//刪一條邊不選邊上一點的情況下,該聯通塊的最大值 
	root=fa[root];
	dp(root);
	ans+=max1(t,max1(f[root][1],f[root][0])); //將兩種情況比較; 
}
int main(){
	n=read();
	int x;
	for(int i=1;i<=n;i++){
		val[i]=read();
		x=read();
		add(x,i);
		fa[i]=x;	
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			find(i);
		}
	}
	cout<<ans;
	return 0;
}

快去ac吧