首页 > 其他分享 >【数据结构】P4338 [ZJOI2018] 历史 题解

【数据结构】P4338 [ZJOI2018] 历史 题解

时间:2023-12-27 10:13:27浏览次数:42  
标签:val int 题解 sza saf fa P4338 ZJOI2018 define

P4338

先考虑怎么安排崛起的先后顺序最优。

但是发现好像没有一个很好的顺序去进行崛起,并且由于 \(a_i\) 的值域会很大,所以即使知道顺序应该也会难以进行维护。

转换一下方向,正难则反。考虑每个点的贡献,但是颜色不同时只会算一次,所以要钦定是哪一个点造成的贡献。令当前考虑的点为 \(u\),发现可以在不影响 \(u\) 的祖先的的贡献的情况下对 \(u\) 子树内的点的相对操作顺序进行改变。所以 \(u\) 点所产生的贡献是很容易计算的:若 \(u\) 以及 \(u\) 的子树内的所有点的 \(a\) 值都没有超过 \(sza_u\) 的一半,则贡献为 \(sza_u-1\)。其中 \(sza_u\) 是 \(u\) 及 \(u\) 的子树的 \(a\) 值的和,减一是因为第一次不会产生贡献。否则就是 \(2(sza_u-mxa_u)\),因为最大的 \(a\) 无法都产生贡献,注意这里的 \(mxa_u\) 需要和 \(a_u\) 取 \(\max\)。

这时候就可以有 \(30\) 分了。

放一份暴力代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define vi vector<int>
#define eb emplace_back
#define pii pair<int, ll>
#define fi first
#define se second
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
bool Mbe;
mt19937_64 rng(35);
constexpr int N = 4e5 + 10;
int n, m;
int sz[N];
ll ans, a[N], sza[N];
vi e[N];
void dfs(int u, int ff) {
	sz[u] = 1, sza[u] = a[u];
	ll mx = a[u];
	for(int v : e[u]) {
		if(v == ff) continue;
		dfs(v, u);
		sz[u] += sz[v];
		sza[u] += sza[v];
		if(sza[v] > mx) mx = sza[v];
	}
	if(2 * mx > sza[u]) ans += 2 * (sza[u] - mx);
	else ans += sza[u] - 1;
}
void work() {
	ans = 0;
	dfs(1, 0);
	cout << ans << "\n";
}
bool Med;
int main() {
	fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
//	freopen("history4.in", "r", stdin);
//	freopen("history.out", "w", stdout);
	ios :: sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	for(int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		e[u].eb(v);
		e[v].eb(u);
	}
	work();
	for(int i = 1; i <= m; ++i) {
		int x, w;
		cin >> x >> w;
		a[x] += w;
		work();
	}
	cerr << TIME << "ms\n";
	return 0;
}

然后考虑对这个暴力进行优化。

对于 \(u\to son\) 的边,若 \(sza_{son}>\dfrac{sza_u}{2}\),则称这条边为实边,否则为虚边。

考虑对 \(v\in\{\text{path}(1,u)\}\) 进行区间加后虚实边会有什么变化。发现一个很美妙的地方,就是整条路径上至多有 \(\log\sum a_i\) 条虚边。然后因为对于一条 \(fa\to x\) 的实边,\(sza_x\) 和 \(sza_{fa}\) 同时增加,\(fa\) 的带权的重儿子显然还是 \(x\)。所以可能发生变化的只有虚边。

于是每次操作至多会修改 \(\mathcal{O}(\log\sum a_i)\) 条边,即需要支持单点修改、查询区间中为 \(1\) 的数,线段树即可。

然后可能会略有卡常,可以将维护 \(sza\) 的数组换为 BIT。

还有一个实现的小技巧,就是因为带权重儿子可能是 \(u\) 本身,所以连一个 \(i\to i+n\) 的边即可,就能把 \(i\) 的点权转到 \(i+n\) 上了,这里借鉴了 _Jxsts 的代码。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define vi vector<int>
#define eb emplace_back
#define pii pair<int, ll>
#define fi first
#define se second
#define TIME 1e3 * clock() / CLOCKS_PER_SEC
bool Mbe;
mt19937_64 rng(35);
constexpr int N = 8e5 + 10;
int n, m, q;
int dfc, sz[N], hv[N], dfn[N], top[N], fa[N], dep[N], rdfn[N], leaf[N], hva[N];
ll ans, a[N], sza[N], val[N];
vi e[N];
void dfs1(int u, int ff) {
	sz[u] = 1, sza[u] = a[u], dep[u] = dep[ff] + 1, fa[u] = ff;
	for(int v : e[u]) {
		if(v == ff) continue;
		dfs1(v, u);
		sz[u] += sz[v];
		sza[u] += sza[v];
		if(sz[v] > sz[hv[u]]) hv[u] = v;
		if(sza[v] > sza[hva[u]]) hva[u] = v;
		++leaf[u];
	}
	if(leaf[u] <= 1) leaf[u] = 1;
	else leaf[u] = 0;
	if(!leaf[u]) {
		if(2 * sza[hva[u]] > sza[u]) val[u] = 2 * (sza[u] - sza[hva[u]]);
		else val[u] = sza[u] - 1;
		ans += val[u];
		// ans += val[u] = min(2 * (sza[u] - sza[hva[u]]), sza[u] - 1);
	}
}
void dfs2(int u, int f) {
	rdfn[dfn[u] = ++dfc] = u, top[u] = f;
	if(!hv[u]) return;
	dfs2(hv[u], f);
	for(int v : e[u]) {
		if(v == hv[u] || v == fa[u]) continue;
		dfs2(v, v);
	}
}
ll v[N];
void add(int x, ll y) {
	for(; x <= m; x += x & -x) v[x] += y;
}
ll ask(int x) {
	ll res = 0;
	for(; x; x -= x & -x) res += v[x];
	return res;
}
ll ask(int l, int r) {
	return ask(r) - ask(l - 1);
}
int sum[N << 2];
void build(int x, int L, int R) {
	if(L == R) {
		sum[x] = 2 * sza[rdfn[L]] <= sza[fa[rdfn[L]]];
		return;
	}
	int m = (L + R) >> 1;
	build(x << 1, L, m);
	build(x << 1 | 1, m + 1, R);
	sum[x] = sum[x << 1] + sum[x << 1 | 1];
}
void modify(int x, int L, int R, int k, int v) {
	if(L == R) {
		sum[x] = v;
		return;
	}
	int m = (L + R) >> 1;
	if(k <= m) modify(x << 1, L, m, k, v);
	else modify(x << 1 | 1, m + 1, R, k, v);
	sum[x] = sum[x << 1] + sum[x << 1 | 1];
}
int query(int x, int L, int R, int l, int r) {
	if(!sum[x]) return -1;
	if(L == R) return rdfn[L];
	int m = (L + R) >> 1;
	if(r > m) {
		int v = query(x << 1 | 1, m + 1, R, l, r);
		if(~v || (m > r)) return v;
	}
	return l <= m ? query(x << 1, L, m, l, r) : -1;
}
void upd(int x, ll v) {
	int tmp = x;
	while(x) {
		add(dfn[top[x]], v), add(dfn[x] + 1, -v);
		x = fa[top[x]];
	}
	x = tmp;
	int r = dfn[x];
	while(x) {
		if(r < dfn[top[x]]) {
			x = fa[top[x]];
			r = dfn[x];
			continue;
		}
		int u = query(1, 1, m, dfn[top[x]], r);
		if(u == -1) {
			x = fa[top[x]];
			r = dfn[x];
			continue;
		}
		ans -= val[fa[u]];
		ll saf = ask(dfn[fa[u]]), sau = ask(dfn[u]), sahv = ask(dfn[hva[fa[u]]]);
		if(u == hva[fa[u]]) {
			if(2 * sahv > saf && 2 * (sahv - v) <= saf - v) modify(1, 1, m, dfn[u], 0);
			if(2 * sahv > saf) val[fa[u]] = 2 * (saf - sahv);
			else val[fa[u]] = saf - 1;
			ans += val[fa[u]];
		} else {
			if(2 * sahv > saf - v && 2 * sahv <= saf) modify(1, 1, m, dfn[hva[fa[u]]], 1);
			if(2 * sau > saf) modify(1, 1, m, dfn[u], 0);
			if(sau > sahv) {
				hva[fa[u]] = u;
				if(sau * 2 > saf) val[fa[u]] = 2 * (saf - sau);
				else val[fa[u]] = saf - 1;
			} else {
				if(sahv * 2 > saf) val[fa[u]] = 2 * (saf - sahv);
				else val[fa[u]] = saf - 1;
			}
			ans += val[fa[u]];
		}
		r = dfn[fa[u]];
	}
}
bool Med;
int main() {
	fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
//	freopen("history.in", "r", stdin);
//	freopen("history.out", "w", stdout);
	ios :: sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> q;
	m = 2 * n;
	for(int i = 1; i <= n; ++i) {
		cin >> a[i + n];
		e[i].eb(i + n);
		e[i + n].eb(i);
	}
	for(int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		e[u].eb(v);
		e[v].eb(u);
	}
	dfs1(1, 0);
	cout << ans << "\n";
	dfs2(1, 1);
	build(1, 1, m);
	for(int i = 1; i <= m; ++i) add(i, sza[rdfn[i]]), add(i + 1, -sza[rdfn[i]]);
//	q = 1; // ...65
	for(int i = 1; i <= q; ++i) {
		int x, w;
		cin >> x >> w;
		upd(x + n, w);
		cout << ans << "\n";
	}
	cerr << TIME << "ms\n";
	return 0;
}

标签:val,int,题解,sza,saf,fa,P4338,ZJOI2018,define
From: https://www.cnblogs.com/Pengzt/p/17929879.html

相关文章

  • CF1896D Ones and Twos 题解
    CF1896D如果只有单次询问其实可以双指针,但是这个难以进行拓展。考虑找点性质。发现\(a_i,v\in\{1,2\}\),从值域上下手。发现若存在和为\(S\)的方案,则一定有和为\(S-2\)的方案,因为可以直接\(-2\)或\(-1-1\)。然后就变为找最大的和为奇/偶数了,因为如果最大的都不行就肯定......
  • P9032 [COCI2022-2023#1] Neboderi 题解
    P9032考试题。发现\(g\)的值是若干个相同的段,且段数很少,因为每次取\(\gcd\)至少会将值域变为原来的一半。所以段数是\(\mathcal{O}(\logV)\)的。然后就可以从小到大枚举左端点,然后枚举\(g\)的值,找的是最远的满足\(\gcd(a_l,\dots,a_r)=g\)的\(r\),这里可以使用二分......
  • [CTSC2018]暴力写挂题解
    我们先将柿子变成\(\frac{1}{2}(dis_{x,y}+dep_{x}+dep_{y})-dep'_{lca'}\)考虑边分治,枚举断边,我们将一个点在第二棵树上的点权看成是\(v_x=d_x+dep_x\),答案就为\(v_x+v_y+dep'_{lca'}\)对于每次边分治将分治联通块内所有点在第二棵树上的建出虚树,同时将分治联通块以分治中心......
  • CF1887D Split 题解
    Problem-D-CodeforcesSplit-洛谷我现在水平好烂,再做下去自信心就全败没了先考虑\(Q=1\)怎么做?两种做法:暴力枚举分界点,左右判断暴力枚举\(\max\limits_{i=l}^{x}a_i\),找到最靠右边的分界点位置\(x\),判断是否\(\max\limits_{i=l}^{x}a_i<\min\limits......
  • [ABC267F] Exactly K Steps 题解
    [ABC267F]ExactlyKSteps题解思路首先发现,如果对于查询\((u,k),k>0\)可行,那么对于\((u,k-1)\)也一定可行,因为往回走一步就可以了,所以对于一个点可以找到离它最远的点,根据直径的结论,这个点一定是直径的端点之一。为了方便做,以直径的端点之一为根,然后写一个K级祖......
  • 【CF30E】Tricky and Clever Password 题解(manacher + exKMP)
    manacher+exKMP+二分。感觉是最粗暴的方法,想出来之后自己硬莽了4k,荣获题解区最长。Solution约定:下文所提及到的所有的回文串,均指奇长度回文串。显然把题目拆成两个部分,中间的回文串,以及两边相同的连续子串。考虑一下从哪个入手比较好。忘记是咋想的了,易得从两边相同......
  • [SNOI2019] 网络 题解
    [SNOI2019]网络题解最喜欢这道题。简要题意给一颗\(n\)个节点的树和一个参数\(d\),定义两个节点\(x,y\)之间的距离为\(x\)到\(y\)的简单路径上的边数。定义一个树上连通块的权值为连通块中任意两点的距离之和。定义一个树上连通块的直径为连通块中任意两点距离的最......
  • CF1887C Minimum Array 题解
    Problem-1887C-CodeforcesMinimumArray-洛谷有点被降智了/ll首先区间修改显然先转化成差分序列单点修改。显然对于相同的操作序列,\(a_i\)的取值对答案无影响,因此我们可以先让\(a_i\)全部取\(0\),最后再加回来即可假如说操作到某一时刻,\(a_i\)的值中第一个......
  • 洛谷B3647 【模板】Floyd 题解 floyd算法 求 多源多汇最短路
    题目链接:https://www.luogu.com.cn/problem/B3647floyd算法:https://oi-wiki.org/graph/shortest-path/#floyd-算法示例程序:#include<bits/stdc++.h>usingnamespacestd;constintmaxn=101;intn,m,f[maxn][maxn];intmain(){scanf("%d%d",&n......
  • [题解]CF1811D Umka and a Long Flight
    思路假设原题目中的\(n\)在本文中为\(num\),则原长方形的长\(m=f_{num+1}\)和宽\(n=f_{num}\)。显然对于最初始的长方形,显然是要将一个\(f_{num}\timesf_{num}\)的长方形丢进去的,并且要么放最左边,要么放在最右边。因为对于当前的\(m=f_{num+1}=f_{num}+......