基环树上dp

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

基环树

先找到图中的环

基环树

然后对于环上的每一个节点为根,对其子树进行树形dp,最后断环成链,对环进行线性dp,

image-20230222171425101

以下面的题目为例

[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 n10

对于 60 % 60\% 60% 的测试数据,满足 n ≤ 100 n \le 100 n100

对于 80 % 80\% 80% 的测试数据,满足 n ≤ 1 0 4 n \le 10 ^4 n104

对于 100 % 100\% 100% 的测试数据,满足 1 ≤ n ≤ 1 0 6 1\le n \le 10^6 1n106,每名骑士的战斗力都是不大于 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]=ysonmax(dp1[y][0],dp1[y][1])dp1[x][1]=(ysondp1[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[j1][0],dp2[j1][1])+dp1[cyc[j]][0]dp2[j][1]=dp2[j1][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[m1][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[m1][0],dp2[m1][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 0n1 编号。

第二行有 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 n100
  • 另有 20 % 20\% 20% 的数据,保证环上的点不超过 2000 2000 2000 个。
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105 1 ≤ p i ≤ 1 0 4 1 \leq p_i \leq 10^4 1pi104 0 ≤ u , v < n 0 \leq u, v < n 0u,v<n 0 ≤ k ≤ 1 0 4 0 \leq k \leq 10^4 0k104 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值