题目链接:BZOJ - 1040
题目分析
这道题目的模型就是一个图,不一定联通,每个连通块的点数等于边数。
每个连通块都是一个基环+外向树。即树上增加了一条边。
如果是树,就可以直接树形DP了。然而这是基环+外向树,需要先找到环上的一条边,记录这条边的两个端点 R1, R2,删掉这条边。
然后分两种情况:一定不选R1;一定不选R2;对这两种情况分别做一次树形DP就可以了。
答案加上这两种情况的答案的较大值。
代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
const int MaxN = 1000000 + 5;
typedef long long LL;
const LL INF = 99999999999999999;
int n, R, R1, R2, Rt;
int A[MaxN];
LL Ans, Temp;
LL F[MaxN][2];
bool Visit[MaxN];
struct Edge
{
int u, v, t;
Edge *Next;
} E[MaxN * 2], *P = E, *Point[MaxN];
inline void AddEdge(int x, int y, int z)
{
++P; P -> u = x; P -> v = y; P -> t = z;
P -> Next = Point[x]; Point[x] = P;
}
inline LL gmax(LL a, LL b) {return a > b ? a : b;}
void DFS(int x, int y)
{
Visit[x] = true;
for (Edge *j = Point[x]; j; j = j -> Next)
{
if (j -> t == y) continue;
if (Visit[j -> v])
{
R1 = x;
R2 = j -> v;
Rt = j -> t;
}
else DFS(j -> v, j -> t);
}
}
void Solve(int x, int y)
{
F[x][0] = 0; F[x][1] = A[x];
for (Edge *j = Point[x]; j; j = j -> Next)
{
if (j -> t == y || j -> t == Rt) continue;
Solve(j -> v, j -> t);
F[x][0] += gmax(F[j -> v][0], F[j -> v][1]);
F[x][1] += F[j -> v][0];
}
if (x == R) F[x][1] = -INF;
}
int main()
{
scanf("%d", &n);
int a;
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", &A[i], &a);
AddEdge(i, a, i);
AddEdge(a, i, i);
}
memset(Visit, 0, sizeof(Visit));
Ans = 0;
for (int i = 1; i <= n; ++i)
{
if (Visit[i]) continue;
DFS(i, 0);
R = R1;
Solve(i, 0);
Temp = gmax(F[i][0], F[i][1]);
R = R2;
Solve(i, 0);
Temp = gmax(Temp, gmax(F[i][0], F[i][1]));
Ans += Temp;
}
printf("%lld\n", Ans);
return 0;
}