BZOJ 1040: [ZJOI2008]骑士(基环树dp)
http://www.lydsy.com/JudgeOnline/problem.php?id=1040
题意:
思路:
这是基环树,因为每个人只会有一个厌恶的人,所以每个节点只会有一个父亲节点,但是根节点也是有父亲节点的,所以在树中肯定是存在一个环的,只要删除该环中的任意一条边,那么就能将该图变成一颗树。
如果是树的话,那就很简单了,d[u][0/1] dp求解即可。
现在假设删除的边是e,两端的节点分别是u,v,首先对u为根的树作一次dp,最后取d[u][0](v取不取都无所谓),不能取d[u][1](因为此时可能也取了v)。但是这样的话没有考虑选u的情况,所以再对v为根的树作一次dp,最后取d[v][0]。两者取大者即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int maxn = 1000000+5;
typedef long long ll;
int n,tot=0,edgeID,edgeLeft,edgeRight;
int head[maxn],vis[maxn];
ll val[maxn], d[maxn][2];
struct node
{
int v,next;
}e[2*maxn];
void addEdge(int u,int v)
{
e[tot].v = v;
e[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u, int fa)
{
vis[u] = 1;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v = e[i].v;
if(v == fa) continue;
if(!vis[v]) dfs(v,u);
else //找到了环
{
edgeID = i; //记录边和两端顶点
edgeLeft = u;
edgeRight = v;
}
}
}
ll dp(int u, int fa)
{
d[u][0] = 0, d[u][1] = val[u];
for(int i=head[u];i!=-1;i=e[i].next)
{
int v = e[i].v;
if(v==fa) continue;
if(i==edgeID || i==(edgeID^1)) continue; //正向边和反向边
dp(v,u);
d[u][0] += max(d[v][0],d[v][1]);
d[u][1] += d[v][0];
}
return d[u][0];
}
int main()
{
//freopen("in.txt","r",stdin);
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%lld%d",&val[i],&x);
addEdge(i,x);
addEdge(x,i);
}
ll ans = 0;
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
dfs(i,-1);
ans += max(dp(edgeLeft,-1),dp(edgeRight,-1));
}
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号