BZOJ 1040: [ZJOI2008]骑士

题目大意:

给定基环外向树森林,每个点有点权,一条边连接的两个点不能同时选取,问选取的点权和最大。

题解:

如果是一棵树,有一个显然树形DP。

如果是基环外向树,那么先在每棵树上DP再在环上DP。

然而这个做法比较麻烦。

于是我们断开环上的一条边,强制一个点不选,就变成了一棵树。

跑两遍树形DP即可

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int flag,n,x,l,r,cnt,tot,last[2000005],vis[2000005],s[2000005];
long long ans,f[2000005][2],val[2000005];
struct node{
	int to,next;
}e[2000005];
void add(int a,int b){
	e[++cnt].to=b;
	e[cnt].next=last[a];
	last[a]=cnt;
}
void dfs(int x,int fa){
	vis[x]=1;
	for (int i=last[x]; i!=-1 && (!flag); i=e[i].next){
		int V=e[i].to;
		if (V!=fa){
			if (vis[V]){
				l=x,r=V;
				s[i]=s[i^1]=-1;
				flag=1;
				break;
			}
			dfs(V,x);
		}
	}
}
void tree_dp(int x,int fa,int ed){
	vis[x]=1;
	if (x!=ed) f[x][1]=val[x];
	else f[x][1]=0;
	f[x][0]=0;
	for (int i=last[x]; i!=-1; i=e[i].next){
		int V=e[i].to;
		if (V==fa) continue;
		if (s[i]==-1) continue;
		tree_dp(V,x,ed);
		f[x][0]+=max(f[V][0],f[V][1]);
		f[x][1]+=f[V][0];
	}
}
int main(){
	cnt=-1;
	memset(last,-1,sizeof(last));
	int n;
	scanf("%d",&n);
	for (int i=1; i<=n; i++){
		int x;
		scanf("%lld%d",&val[i],&x);
		add(x,i);
		add(i,x);
	}
	for (int i=1; i<=n; i++)
		if (!vis[i]){
			flag=0;
			dfs(i,0);
			long long maxx=0;
			tree_dp(l,0,r);
			maxx=max(f[l][0],f[l][1]);
			tree_dp(r,0,l);
			maxx=max(maxx,max(f[r][0],f[r][1]));
			ans+=maxx;
		}
	printf("%lld\n",ans);
	return 0;
}

  

 

posted @ 2018-07-22 20:32  ~Silent  阅读(143)  评论(0编辑  收藏  举报
Live2D