对于任意一棵基环树,它的长相是这样的

先找到图中的环

然后对于环上的每一个节点为根,对其子树进行树形dp,最后断环成链,对环进行线性dp,
以下面的题目为例
[ZJOI2008] 骑士
题目描述
Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。
最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的 Z 国又怎能抵挡的住 Y 国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。
骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。
战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。
为了描述战斗力,我们将骑士按照 1 1 1 至 n n n 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。
输入格式
第一行包含一个整数 n n n,描述骑士团的人数。
接下来 n n n 行,每行两个整数,按顺序描述每一名骑士的战斗力和他最痛恨的骑士。
输出格式
应输出一行,包含一个整数,表示你所选出的骑士军团的战斗力。
样例 #1
样例输入 #1
3
10 2
20 3
30 1
样例输出 #1
30
提示
数据规模与约定
对于 30 % 30\% 30% 的测试数据,满足 n ≤ 10 n \le 10 n≤10;
对于 60 % 60\% 60% 的测试数据,满足 n ≤ 100 n \le 100 n≤100;
对于 80 % 80\% 80% 的测试数据,满足 n ≤ 1 0 4 n \le 10 ^4 n≤104。
对于 100 % 100\% 100% 的测试数据,满足 1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1≤n≤106,每名骑士的战斗力都是不大于 1 0 6 10^6 106 的正整数。
思路
由题意得,这道题是一个基环树森林,所以拆成每一个基环树来做。
以环上的每一个点为根做树形dp,设
d
p
1
[
x
]
[
0
/
1
]
dp1[x][0/1]
dp1[x][0/1]表示在以节点
x
x
x为根的子树内,不选或者选点
x
x
x的最大攻击力。设
y
y
y为
x
x
x在环外的子节点,那么明显方程为
d
p
1
[
x
]
[
0
]
=
∑
y
∈
s
o
n
m
a
x
(
d
p
1
[
y
]
[
0
]
,
d
p
1
[
y
]
[
1
]
)
d
p
1
[
x
]
[
1
]
=
(
∑
y
∈
s
o
n
d
p
1
[
y
]
[
1
]
)
+
w
e
i
g
h
t
[
x
]
dp1[x][0]=\sum_{y\in son} max(dp1[y][0], dp1[y][1])\\ dp1[x][1]=(\sum_{y\in son}dp1[y][1])+weight[x]
dp1[x][0]=y∈son∑max(dp1[y][0],dp1[y][1])dp1[x][1]=(y∈son∑dp1[y][1])+weight[x]
断环成链,对环上的点进行线性dp,注意需要枚举端点选或不选的情况,如果起点有士兵,则终点不能放士兵;否则终点可放可不放士兵,
c
y
c
[
j
]
cyc[j]
cyc[j]对应环上的结点编号
d
p
2
[
j
]
[
0
]
=
m
a
x
(
d
p
2
[
j
−
1
]
[
0
]
,
d
p
2
[
j
−
1
]
[
1
]
)
+
d
p
1
[
c
y
c
[
j
]
]
[
0
]
d
p
2
[
j
]
[
1
]
=
d
p
2
[
j
−
1
]
[
0
]
+
d
p
1
[
c
y
c
[
j
]
]
[
1
]
dp2[j][0] = max(dp2[j - 1][0], dp2[j - 1][1]) + dp1[cyc[j]][0]\\ dp2[j][1] = dp2[j - 1][0] + dp1[cyc[j]][1]
dp2[j][0]=max(dp2[j−1][0],dp2[j−1][1])+dp1[cyc[j]][0]dp2[j][1]=dp2[j−1][0]+dp1[cyc[j]][1]
起点有士兵时
a
n
s
=
m
a
x
(
a
n
s
,
d
p
2
[
m
−
1
]
[
0
]
)
ans = max(ans, dp2[m - 1][0])
ans=max(ans,dp2[m−1][0])
起点没有士兵时
a
n
s
=
m
a
x
(
d
p
2
[
m
−
1
]
[
0
]
,
d
p
2
[
m
−
1
]
[
1
]
)
ans = max(dp2[m - 1][0], dp2[m - 1][1])
ans=max(dp2[m−1][0],dp2[m−1][1])
#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<unordered_map>
#include<unordered_set>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<random>
#include<ctime>
#include<cstring>
#include<cstdio>
#include<cstring>
#include<bitset>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
int w[N], fa[N], vis[N], oncyc[N];
ll dp1[N][2], dp2[N][2];
vector<vector<int>> e;
void dfs(int x) {
// 选择该点,加上权值
dp1[x][1] = w[x];
vis[x] = 1;
for (int i = 0; i < e[x].size(); i++) {
int v = e[x][i];
if (oncyc[v]) continue;
dfs(v);
dp1[x][0] += max(dp1[v][0], dp1[v][1]);
dp1[x][1] += dp1[v][0];
}
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int n;
ll res = 0;
cin >> n;
e.resize(n + 1);
for (int i = 1; i <= n; i++) {
cin >> w[i] >> fa[i];
e[fa[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (vis[i]) continue;
int cur = i;
// 找到环上的一点
while (!vis[cur]) {
vis[cur] = 1;
cur = fa[cur];
}
vector<int> cyc;
int p = cur;
// 记录环上所有点
while (1) {
cyc.push_back(p);
oncyc[p] = 1;
p = fa[p];
if (p == cur) break;
}
// 对环上所有点及其环外孩子进行树形dp,注意树形dp要排除掉cyc[i]在环上的孩子结点
for (int j = 0; j < cyc.size(); j++) {
dfs(cyc[j]);
}
int m = cyc.size();
memset(dp2, 0, sizeof(dp2));
// 断环成链,枚举起点是否有士兵
// 起点没有士兵
dp2[0][0] = dp1[cyc[0]][0];
for (int j = 1; j < m; j++) {
dp2[j][0] = max(dp2[j - 1][0], dp2[j - 1][1]) + dp1[cyc[j]][0];
dp2[j][1] = dp2[j - 1][0] + dp1[cyc[j]][1];
}
ll ans = max(dp2[m - 1][0], dp2[m - 1][1]);
memset(dp2, 0, sizeof(dp2));
// 起点有士兵
dp2[0][1] = dp1[cyc[0]][1];
for (int j = 1; j < m; j++) {
dp2[j][0] = max(dp2[j - 1][0], dp2[j - 1][1]) + dp1[cyc[j]][0];
dp2[j][1] = dp2[j - 1][0] + dp1[cyc[j]][1];
}
ans = max(ans, dp2[m - 1][0]);
res += ans;
}
cout << res;
return 0;
}
城市环路
题目背景
一座城市,往往会被人们划分为几个区域,例如住宅区、商业区、工业区等等。
B 市就被分为了以下的两个区域——城市中心和城市郊区。在这两个区域的中间是一条围绕 B 市的环路,环路之内便是 B 市中心。
题目描述
整个城市可以看做一个 n n n 个点, n n n 条边的单圈图(保证图连通),唯一的环便是绕城的环路。保证环上任意两点有且只有 2 2 2 条简单路径互通。图中的其它部分皆隶属城市郊区。
现在,有一位名叫 Jim 的同学想在 B 市开店,但是任意一条边的 2 2 2 个点不能同时开店,每个点都有一定的人流量,第 i i i 个点的人流量是 p i p_i pi,在该点开店的利润就等于 p i × k p_i×k pi×k,其中 k k k 是一个常数。
Jim 想尽量多的赚取利润,请问他应该在哪些地方开店?
输入格式
第一行一个整数 n n n,代表城市中点的个数。城市中的 n n n 个点由 0 ∼ n − 1 0 \sim n-1 0∼n−1 编号。
第二行有 n n n 个整数,第 ( i + 1 ) (i + 1) (i+1) 个整数表示第 i i i 个点的人流量 p i p_i pi。
接下来 n n n 行,每行有两个整数 u , v u, v u,v,代表存在一条连接 u u u 和 v v v 的道路。
最后一行有一个实数,代表常数 k k k。
输出格式
输出一行一个实数代表答案,结果保留一位小数。
样例 #1
样例输入 #1
4
1 2 1 5
0 1
0 2
1 2
1 3
2
样例输出 #1
12.0
提示
数据规模与约定
- 对于 20 % 20\% 20% 的数据,保证 n ≤ 100 n \leq 100 n≤100。
- 另有 20 % 20\% 20% 的数据,保证环上的点不超过 2000 2000 2000 个。
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105, 1 ≤ p i ≤ 1 0 4 1 \leq p_i \leq 10^4 1≤pi≤104, 0 ≤ u , v < n 0 \leq u, v < n 0≤u,v<n, 0 ≤ k ≤ 1 0 4 0 \leq k \leq 10^4 0≤k≤104, k k k 的小数点后最多有 6 6 6 位数字。
思路
跟骑士那题本质相同,但本题只有一棵基环树,且是无向图,需要dfs找环。然后进行环外树形dp,环上线性dp即可
#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<unordered_map>
#include<unordered_set>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<random>
#include<ctime>
#include<cstring>
#include<cstdio>
#include<cstring>
#include<bitset>
using namespace std;
const int N = 1e5 + 5;
typedef long long ll;
int p[N], vis[N], oncyc[N];
ll dp1[N][2], dp2[N][2];
vector<int> stk, cyc;
vector<vector<int>> e;
bool dfs1(int x, int fa) {
if (vis[x]) {
cyc.push_back(x);
oncyc[x] = 1;
while (stk.back() != x) {
cyc.push_back(stk.back());
oncyc[stk.back()] = 1;
stk.pop_back();
}
return true;
}
stk.push_back(x);
vis[x] = 1;
for (int i = 0; i < e[x].size(); i++) {
int v = e[x][i];
if (v == fa) continue;
if (dfs1(v, x)) return true;
}
stk.pop_back();
return false;
}
void dfs2(int x, int fa) {
dp1[x][1] = p[x];
for (int i = 0; i < e[x].size(); i++) {
int v = e[x][i];
if (v == fa) continue;
if (oncyc[v]) continue;
dfs2(v, x);
dp1[x][1] += dp1[v][0];
dp1[x][0] += max(dp1[v][1], dp1[v][0]);
}
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(0);
int n;
cin >> n;
e.resize(n);
for (int i = 0; i < n; i++) cin >> p[i];
for (int i = 0; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
double k;
cin >> k;
dfs1(0, -1);
int m = cyc.size();
for (int i = 0; i < m; i++) {
dfs2(cyc[i], -1);
}
dp2[0][0] = dp1[cyc[0]][0];
for (int i = 1; i < m; i++) {
dp2[i][0] = max(dp2[i - 1][0], dp2[i - 1][1]) + dp1[cyc[i]][0];
dp2[i][1] = dp2[i - 1][0] + dp1[cyc[i]][1];
}
ll ans = max(dp2[m - 1][0], dp2[m - 1][1]);
dp2[0][0] = 0, dp2[0][1] = dp1[cyc[0]][1];
for (int i = 1; i < m; i++) {
dp2[i][0] = max(dp2[i - 1][0], dp2[i - 1][1]) + dp1[cyc[i]][0];
dp2[i][1] = dp2[i - 1][0] + dp1[cyc[i]][1];
}
ans = max(dp2[m - 1][0], ans);
k *= ans;
printf("%.1f", k);
return 0;
}