bzoj 1040 [ZJOI2008]骑士(基环外向树,树形DP)

时间:2022-12-12 17:01:08

【题目链接】

http://www.lydsy.com/JudgeOnline/problem.php?id=1040

【题意】

给一个基环森林,每个点有一个权值,求一个点集使得点集中的点无边相连且权值和最大。

【思路】

注意题目中的有向边其实就是无向边。然后有多个联通块,每个联通块中有且仅有一个环。

如果没有环的话可以用树形DP,解决这个问题。

设f[i][0],f[i][1]分别表示以i为根,不选/选i时的最大权值。则有转移式:

f[i][0]=sigma{ max(f[son(i)][0],f[son(i)][1]) }

f[i][1]=sigma{ f[son(i)][0] }

对于一个环,我们任选一条边拆开,然后以边的两点U,V为根做树形DP,再考虑边UV存在,有两种情况:

  1) 强制不选U,V任意,环的贡献为以U做DP的f[U][0]

  2) 强制不选V,U任意,环的贡献为以V做DP的f[V][0]

【科普】

基环外向树就是一棵树加一条边(好厉害的名字<_<

【代码】

 #include<cstdio>
#include<cstring>
#include<iostream>
#define FOR(a,b,c) for(int a=(b);a<=(c);a++)
using namespace std; typedef long long ll;
const int N = 1e6+; struct Edge {
int v,nxt;
}e[N<<];
int en=,front[N];
void adde(int u,int v)
{
en++; e[en].v=v,e[en].nxt=front[u],front[u]=en;
} int n,w[N],vis[N];
ll f[N][]; ll read()
{
char c=getchar(); ll f=,x=;
while(!isdigit(c)) {if(c=='-') f=-; c=getchar(); };
while(isdigit(c)) x=x*+c-'',c=getchar();
return x*f;
} int U,V,E;
void dfs(int u,int fa)
{
vis[u]=;
for(int i=front[u];i;i=e[i].nxt) {
if((i^)==fa) continue;
int v=e[i].v;
if(vis[v]) {
U=u; V=v; E=i;
continue;
}
dfs(v,i);
}
}
void treedp(int u,int fa,int ban)
{
f[u][]=w[u],f[u][]=;
for(int i=front[u];i;i=e[i].nxt) {
if((i^)==fa) continue;
if(i==ban||(i^)==ban) continue;
int v=e[i].v;
treedp(v,i,ban);
f[u][]+=max(f[v][],f[v][]);
f[u][]+=f[v][];
}
} int main()
{
n=read();
int v;
FOR(i,,n) {
w[i]=read(),v=read();
adde(i,v),adde(v,i);
}
ll ans=;
FOR(i,,n) if(!vis[i]) {
dfs(i,-);
treedp(U,-,E);
ll tmp=f[U][];
treedp(V,-,E);
tmp=max(tmp,f[V][]);
ans+=tmp;
}
printf("%lld",ans);
return ;
}