题目链接:https://loj.ac/problem/10162
题目大意
每个骑士都有一个仇人,问要怎么选择骑士团成员,使成员不会有仇人在内,并且战斗力最大。
解题思路
如果这道题是一棵树,那么就是树的最大独立集的模板题,但是本题是一个有许多独立联通块的图,并且每个联通块必定是有且仅有一个环。为什么呢?因为骑士和仇人只会构成一条边,那就是一个点只会有一条边出去指向仇人,所以n个点会有n条边而且是同一联通块,那么必定是有且仅有一个环。知道这个之后,我们再看如果按照树的最大独立集的方法去做有什么问题。问题就是,在一个环上,如果以其中一个点为子树的根去dp,那么到了环上的最后一个点(即再指出便回到根),那么就可能会被选上。解决办法,就是选出环上的第一个点和最后一个点,分别作为根去dp,这样我们选择时,选择max(dp[U][0],dp[V][0])。dp[i][0]表示以i为子树的根不选i的最大值,dp[i][1]表示以i为子树的根选i的最大值。这样就能避免冲突。注意,我们再以V作为根去dp时会更新dp[U][0],所以要把之前的dp[U][0]保存下来,防止被覆盖。
AC代码
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
ll dp[maxn][2];//dp[i][0]以i为根的子图不选i最大值dp[i][1]以i为根的子图选i最大值
bool vis[maxn];
int n,cnt;
ll a[maxn];
int U,V,E;
struct node
{
int to,next;
}edge[maxn<<1];
int head[maxn];
void add(int x,int y)
{
edge[++cnt].to=y;
edge[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int u,int fa)
{
vis[u]=1;
for(int i=head[u];~i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa)
continue;
if(vis[v])
{
U=u;
V=v;
E=i;
continue;
}
dfs(v,u);
}
}
void solve(int u,int fa)
{
dp[u][0]=0;
dp[u][1]=a[u];
for(int i=head[u];~i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||i==E||(i^1)==E)
continue;
solve(v,u);
dp[u][0]+=max(dp[v][0],dp[v][1]);
dp[u][1]+=dp[v][0];
}
}
int main()
{
memset(head,-1,sizeof(head));
cnt=-1;
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
int x;
scanf("%d%d",&a[i],&x);
add(i,x);
add(x,i);
}
ll ans=0;
for(int i=1;i<=n;++i)
{
if(!vis[i])
{
dfs(i,-1);
solve(U,-1);
ll tmp=dp[U][0];//记录一下dp[U][0]之和后会被更新
solve(V,-1);
ans+=max(tmp,dp[V][0]);
}
}
printf("%lld\n",ans);
return 0;
}