原题:
题目描述
Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的Z国又怎能抵挡的住Y国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照1至N编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
输入格式:
n第一行包含一个正整数N,描述骑士团的人数。
接下来N行,每行两个正整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
输出格式:
包含一个整数,表示你所选出的骑士军团的战斗力。
题目大意
给定n个点,从每个点出发都引一条边,且每个点都有一定的点权,你的任务是选出一个点集,使得这个集合里的点在保证两两没有连边的情况下点权和最大。
题解:
首先我们应该意识到,这个n个点n条边的图肯定具有特殊的性质,先假设这个图是联通的,由于n个点n-1条边恰好是一颗树,那么在树上任意两点连上一条边,那么就恰好形成一个环,这不恰恰就是基环外向树吗?
但这一切只是在连通图的前提下,那么,这个图有没有可能是联通的呢?当然有可能,我们考虑将两棵基环外向树放在一起,它仍然满足边的数量等于点的数量,并且它是由两棵独立的基环外向树组成的,因此不连通。那么,有没有一个这样的图G,它由若干个独立的子图构成,且这些子图中有的不是基环外向树,并满足G的边的数量等于点的数量呢?
答案是否定的,我们使用反证法:假如有一个n条边n个点构成的非连通图由包含非基环外向树的独立子图构成,不妨设这些子图本身是连通图(如果不是联通的我们将它分为多个子图看待),并且先删去其中所有的基环外向树,那么剩余的子图的点的总数一定等于边的总数,且其中不含有基环外向树,也就是对于剩余的每一个子图都有边的数量不等于点的数量,那么肯定存在一个边的数量少于点的数量的子图(若不存在,则剩余子图边数和大于点数和,原图的边数和大于点数和,不符合题意),而由于这子图是联通的,那么它肯定是树。而树中有n-1条边,则不满足从每个点出发都引一条边的性质,与题意矛盾,故假设不成立。
所以我们现在就知道了这个图一定是有一个或多个基环外向树构成的,那么对于每一棵基环外向树 ,我们可以用并查集维护其中的联通块,那么一定有且只有一条边的两端在该连这条边下处于同一联通块内,我们就不连接这条边,并记录这两个点。然后我们在每一棵基环外向树中分别以这两个记录的点为根节点都做一次树形DP。
dp[x][0]表示在x的子树中不选x一共选出的点权最大和。
dp[x][1]表示在x的子树中选x一共选出的点权最大和。
考虑转移:dp[x][0]可以从每一个子节点y的max(dp[x][0],dp[x][1])转移,dp[x][1]可以从每一个子节点的dp[x][0]转移。
对于每一颗基环外向树,设它记录的两个点分别为a、b,我们分别以a、b为根做一次树形DP,给最终答案加上max(dp[a][0],dp[b][0]),因为这样保证a、b至少有一个不选(因为原来没有连的那条边的两个端点分别为a、b),最后的最终答案就是这道题应该输出的结果。
AC代码如下
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 1000020 using namespace std; LL read(){ LL nm=0ll,fh=1ll;char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw==‘-‘) fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10ll+(cw-‘0‘); return nm*fh; } LL n,m,tmp,w[M],dp[M][2],f[M],nt[M<<1],to[M<<1]; LL fa[M],t,pt,mt[2][M],ans[M],cnt,sum; LL find(LL x){return x==fa[x]?x:fa[x]=find(fa[x]);} bool conn(LL x,LL y){ LL a=find(x),b=find(y); if(a==b){mt[0][++cnt]=x,mt[1][cnt]=y;return false;} fa[a]=b;return true; } void link(LL x,LL y){nt[tmp]=f[x],f[x]=tmp,to[tmp++]=y;} void dfs(LL x,LL last){ dp[x][0]=0,dp[x][1]=w[x]; for(LL i=f[x];i!=-1;i=nt[i]){ if(to[i]==last) continue; dfs(to[i],x); dp[x][0]+=max(dp[to[i]][0],dp[to[i]][1]); dp[x][1]+=dp[to[i]][0]; } } int main(){ n=read(),memset(f,-1,sizeof(f)); for(LL i=1;i<=n;i++) fa[i]=i; for(LL i=1;i<=n;i++){ w[i]=read(),pt=read(); if(conn(i,pt)) link(i,pt),link(pt,i); } for(LL i=1;i<=cnt;i++) dfs(mt[0][i],-1),ans[i]=dp[mt[0][i]][0]; memset(dp,0,sizeof(dp)); for(LL i=1;i<=cnt;i++) dfs(mt[1][i],-1),sum+=max(ans[i],dp[mt[1][i]][0]); printf("%lld\n",sum); return 0; }