[ZJOI2008] 骑士

link

基环树板子。突然想起了一年以前的那个傍晚和黄忠公园的蚊子。往事不堪回首。

基环树找环采用了题解区的一种比较好理解的方法,即拓扑排序。无向图中不断去除度为1的结点,剩下的一定都是环上的结点(虽然可能是基环树森林)。然后可以枚举环上的结点,每次solve都先找环,然后求出这个点对应的树的答案。有一些特殊情况,比如只有两个点的环。代码如下:

int now=wh,cnt=0;
do{
	c[++cnt]=now;dfs(now,0);
	for(int i=head[now],th;i;i=e[i].next){
		if(tr[th=e[i].t]||th==c[cnt-1])continue;
		now=th;break;
	}
}while(now!=c[cnt]&&now!=wh);

dp部分就比较简单了。注意断环成链时链首和链尾不能同时选,需要强制一个不选之后再进行环上dp。有一些细节要注意,但总体而言还是比较好写的。

#include<bits/stdc++.h>
//#define feyn
#define int long long
const int N=1000010;
using namespace std;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline void check(int &s1,int s2){
	s1=s1<s2?s2:s1;return;
}
inline int max(int s1,int s2){
	return s1<s2?s2:s1;
}

int m,a[N],ans;

struct edge{
	int t,next;
}e[N<<1];
int head[N],esum;
inline void add(int fr,int to){
	e[++esum]=(edge){to,head[fr]};head[fr]=esum;
}

int d[N];bool tr[N];
queue<int>q;
void find_circle(){
	for(int i=1;i<=m;i++){
		if(d[i]==1)q.push(i);
	}
	while(!q.empty()){
		int wh=q.front();q.pop();tr[wh]=true;//printf("wh=%lld\n",wh);
		for(int i=head[wh],th;i;i=e[i].next){
			d[th=e[i].t]--;
			if(d[th]==1)q.push(th);
		}
	}
	return;
}

int c[N],cnt;

int f[N],g[N],r[N][2];//选不选自己 
inline void dfs(int wh,int fa){
	for(int i=head[wh],th;i;i=e[i].next){
		if(tr[th=e[i].t]==false||th==fa)continue;
		dfs(th,wh);
		g[wh]+=max(f[th],g[th]);
		f[wh]+=g[th];
	}
	f[wh]+=a[wh];
}
inline void solve(int wh){
	int now=wh,an=0;cnt=0;
	do{
		c[++cnt]=now;dfs(now,0);
		for(int i=head[now],th;i;i=e[i].next){
			if(tr[th=e[i].t]||th==c[cnt-1])continue;
			now=th;break;
		}
	}while(now!=c[cnt]&&now!=wh);
	
	r[c[1]][0]=g[c[1]],r[c[1]][1]=f[c[1]];now=c[1];
	for(int i=2;i<cnt;i++){
		now=c[i];int last=c[i-1];
		r[now][1]=f[now]+r[last][0];
		r[now][0]=g[now]+max(r[last][0],r[last][1]);
	}
	check(an,max(r[now][0],r[now][1])+g[c[cnt]]);
	
	r[c[cnt]][0]=g[c[cnt]],r[c[cnt]][1]=f[c[cnt]];now=c[cnt];
	for(int i=cnt-1;i>=2;i--){
		now=c[i];int last=c[i+1];
		r[now][1]=f[now]+r[last][0];
		r[now][0]=g[now]+max(r[last][0],r[last][1]);
	} 
	check(an,max(r[now][0],r[now][1])+g[c[1]]);
	
	//for(int i=1;i<=cnt;i++)printf("%lld ",c[i]);printf("\n");
	
	ans+=an;
	for(int i=1;i<=cnt;i++)tr[c[i]]=true;
}

signed main(){
	
	#ifdef feyn
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);int s1;
	for(int i=1;i<=m;i++){
		read(a[i]);read(s1);
		add(i,s1);add(s1,i);
		d[i]++;d[s1]++;
	}
	find_circle();
	for(int i=1;i<=m;i++){
		if(tr[i])continue;
		solve(i);
	}
	printf("%lld\n",ans);
	
	return 0;
}
posted @ 2022-07-19 17:33  Feyn618  阅读(13)  评论(0编辑  收藏  举报