题目链接
题意:给你
个点和
条有向边,每个点有点权,保证每个点只有出度都是
,不保证连通,保证没有自环,要求点
所能到达的点和能到达点
的点不能同时被选,选出若干个点,使他们的权值和最大。
题解:
我们发现每个点只有一条出边,那么有点像一棵树(每个点最多只有一个父节点),由于不一定连通,所以实际应该说是一个由若干基环树组成的森林,因为每个连通块都不会存在一个没有出度的点成为根,所以根一定是一个环。但是和树的区别是可能会有环。但是我们发现只会存在简单环,不会出现环套环,原因是每个点的出度是
,要形成环套环的话一定会在环上有某个点的出度大于
。由此我们考虑一下可以发现,对于每个连通块,环只可能出现在该连通块的“根”,也就是连通块只有根的地方可能是环。这里的连通块不是有向图中tarjan意义上能互相到达的连通,而是把有向边看作无向边之后的连通情况。只有根能出现环的原因是,假如有环并且环不是该连通块的根,那么环上一定有一个点的父节点不在环上,又因为形成了一个环,所以这个节点一定还有一个节点在环上,这与每个点只有一个出度矛盾,所以证明了有环的话环一定出现在连通块的根上。
其实拿到这道题后不难发现,假如这道题没有环,那么就变成了那道叫anniversary party(没有上司的舞会)的题了。我们可以设
为不选
点的最大权值和,
是选
点的最大权值和,那么转移方程是
简单解释一下,就是根据题目要求,假如不选点 ,那么 的儿子选不选都可以;假如选了点 ,那么 的儿子一定不能同时选。
那么现在我们就来解决有环的问题。我们对于一个没有访问过的点,不断地找它的父节点,并标记为访问过的点,那么最后一定会到达这个连通块的根,也就是环上的一个点。我们知道,遇到环的问题时一个常用的办法是破环为链,那么我们对于每一个连通块,找到环上一个点,我们把这个点与其父节点的边断开,这样就形成了一棵树。然后分别把这个点和它断开前的父节点分别作为树的根进行上述的树形dp,在dp过程中这两个点强制只能选一个,然后对两种情况取一个最大值作为这个连通块最大权值和。
这样我们可以求出每一个连通块的最大权值和,那么最终答案就是每个连通块答案的和,因为连通块之间是没有任何影响的。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,val[6002],fa[6002],hed[10001],cnt,dp[6003][2],rt=1;
struct node
{
int to,next;
}a[50001];
void add(int from,int to)
{
a[++cnt].to=to;
a[cnt].next=hed[from];
hed[from]=cnt;
}
void dfs(int x)
{
for(int i=hed[x];i;i=a[i].next)
{
dfs(a[i].to);
dp[x][1]+=dp[a[i].to][0];
dp[x][0]+=max(dp[a[i].to][1],dp[a[i].to][0]);
}
}
int main()
{
while(~scanf("%d",&n))
{
memset(fa,0,sizeof(fa));
memset(dp,0,sizeof(dp));
cnt=0;
memset(hed,0,sizeof(hed));
memset(val,0,sizeof(val));
for(int i=1;i<=n;++i)
scanf("%d",&dp[i][1]);
int x,y;
while(~scanf("%d%d",&x,&y))
{
if(x==0&&y==0)
break;
add(y,x);
fa[x]=y;
}
while(fa[rt])
rt=fa[rt];
dfs(rt);
printf("%d\n",max(dp[rt][0],dp[rt][1]));
}
return 0;
}