发布于  更新于 

洛谷P2607 [ZJOI2008]骑士

DP 题解 OI

经典题,n个点n条边的图一定是一个基环树森林,于是可以先DFS一遍把环找出来,再断环(在环上随便找一条边,强制两个端点之一不选),就变成了一个没有上司的舞会了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;

typedef long long int64;

const int MAXN = 1e6+10;
const int INF = 0x3f3f3f3f;

class lfs {
public:
int w[MAXN];
lfs(int N) {
memset(head, -1, sizeof head);
memset(e,0,sizeof e);
ecnt = 0;
n = N;
}
void adde(int from, int to) {
e[ecnt].to = to;
e[ecnt].next = head[from];
head[from] = ecnt++;
}
void addde(int a, int b) {
adde(a, b);
adde(b, a);
}

int64 solve(){
memset(vis,false,sizeof vis);
memset(dp,0,sizeof dp);
int64 ans = 0;
for(int i = 1;i<=n;i++){
if(!vis[i]){
dfs1(i,0);
dfs2(e[edge_to_break].to,0);
int64 now = dp[e[edge_to_break].to][0];
dfs2(e[edge_to_break^1].to,0);
now = max(now,dp[e[edge_to_break^1].to][0]);
ans += now;
}
}
return ans;
}

protected:
struct Edge {
int to, next;
} e[MAXN * 2];
int head[MAXN];
int ecnt;
int n;

private:
bool vis[MAXN];
int64 dp[MAXN][2];
int edge_to_break;

void dfs1(int u,int fa) {
vis[u] = true;
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (v != fa) {
if(vis[v]){
edge_to_break = i;
continue; // <============!!!
}
dfs1(v,u);
}
}
}

void dfs2(int u, int fa) {
dp[u][0] = 0;
dp[u][1] = w[u];
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (v != fa && i != edge_to_break && (i^1) != edge_to_break) {
dfs2(v, u);
dp[u][0] += max(dp[v][0],dp[v][1]);
dp[u][1] += dp[v][0];
}
}
// clog << "dp[" << u << "][0] = " << dp[u][0] << endl;
// clog << "dp[" << u << "][1] = " << dp[u][1] << endl;
}
};

#include <cctype>
#include <cstdio>

inline int read() {
int X = 0, w = 0;
char ch = 0;
while (!isdigit(ch)) {
w |= ch == '-';
ch = getchar();
}
while (isdigit(ch)) {
X = (X << 3) + (X << 1) + (ch ^ 48);
ch = getchar();
}
return w ? -X : X;
}

int main(){
// freopen("data","r",stdin);
int n = read();
lfs* tree = new lfs(n);
for(int i = 1;i<=n;i++){
tree->w[i] = read();
tree->addde(i,read());
}
cout << tree->solve() << endl;
}

有一个细节问题是标记处的continue,如果改为return就会引起无限递归爆栈,不知道为什么。