洛谷 P9047 [PA2021] Poborcy podatkowi

题意

给一棵有边权的树,从中选出若干条长度为 4 的路径,要求边不交,求最大权值和。

数据范围:\(1\le n\le 2\times 10^5, -10^9\le w\le 10^9\)

题解

考虑朴素平方做法:设 \(f_{i, 0/1/2/3}\) 表示 \(i\) 的子树内的答案,同时记录 \(i\) 上可能有的一条不完整路径的长度。在每个点处合并儿子,注意到具体谁和谁拼接不太重要,进行一个 DP,记录当前长度为 2 路径的奇偶性,以及长度为 1,3 路径的个数差,就可以做到 \(n^2\)。瓶颈在于长度 1,3 的路径的拼接。

一个结论:数轴上从原点开始随机游走,每次等概率左右移动,最终到达的最远的点到原点的距离期望是根号级别。考虑随机打乱儿子,那么对于最优解的 DP 路径,长度 1,3 路径的个数差始终在儿子个数的平方根的几倍以内的概率非常大,那么我们 DP 数组只开这么大,得到答案多半也是对的。这样,时间复杂度优化为 \(\Theta(n\sqrt{n})\)

代码

// Author: kyEEcccccc

#include <bits/stdc++.h>

using namespace std;

using LL = long long;
using ULL = unsigned long long;

#define F(i, l, r) for (int i = (l); i <= (r); ++i)
#define FF(i, r, l) for (int i = (r); i >= (l); --i)
#define MAX(a, b) ((a) = max(a, b))
#define MIN(a, b) ((a) = min(a, b))
#define SZ(a) ((int)((a).size()) - 1)

const int N = 200005, B = 1000;

int n;
vector<pair<int, LL>> e[N];
mt19937 ran(chrono::system_clock::now().time_since_epoch().count());
LL f[N][4], g[2][B * 2 + 5][2];

void dp(int u, int par, LL wp)
{
	for (auto pi : e[u])
	{
		if (pi.first == par) continue;
		dp(pi.first, u, pi.second);
	}
	int pre_i = 0, cur_i = 1;
	memset(g, 0xc0, sizeof (g));
	g[0][B + 1][0] = 0;
	for (auto pi : e[u])
	{
		if (pi.first == par) continue;
		int v = pi.first;
		F(i, 1, B * 2 + 1) F(t, 0, 1)
		{
			MAX(g[cur_i][i][t], g[pre_i][i][t ^ 1] + f[v][2]);
			MAX(g[cur_i][i][t], g[pre_i][i - 1][t] + f[v][1]);
			MAX(g[cur_i][i][t], g[pre_i][i + 1][t] + f[v][3]);
			MAX(g[cur_i][i][t], g[pre_i][i][t] + f[v][0]);
		}
		swap(pre_i, cur_i);
	}
	MAX(f[u][0], g[pre_i][B + 1][0]);
	MAX(f[u][0], g[pre_i][B][0] + wp);
	MAX(f[u][1], g[pre_i][B + 1][0] + wp);
	MAX(f[u][2], g[pre_i][B + 2][0] + wp);
	MAX(f[u][3], g[pre_i][B + 1][1] + wp);
}

signed main(void)
{
	// freopen(".in", "r", stdin);
	// freopen(".out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(nullptr);

	cin >> n;
	F(i, 1, n - 1)
	{
		int u, v; LL w; cin >> u >> v >> w;
		e[u].emplace_back(v, w); e[v].emplace_back(u, w);
	}
	F(u, 1, n) shuffle(e[u].begin(), e[u].end(), ran);
	memset(f, 0xc0, sizeof (f));
	dp(1, 0, LLONG_MIN / 2);
	cout << f[1][0] << endl;
	
	return 0;
}
posted @ 2023-03-24 16:28  kyEEcccccc  阅读(56)  评论(0编辑  收藏  举报