ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

NFLSOJ 10267 - 「2020NOIP模拟题-中山纪念2」铲雪(树链剖分+树状数组+势能分析)

2022-06-12 10:02:37  阅读:153  来源: 互联网

标签:剖分 NFLSOJ fa int top 模拟题 dep MAXN SUMB


题面传送门

怎么场均一道不可写题啊.jpg

首先看到这样的题面我们可以想到 AGC010C Cleaning,因此我们第一反应肯定是用路径合并的思想去解决这个问题,尝试写一个程序后你会发现每个点向上延伸出去的路径数量就固定的:为其与父亲相连的边的权值。因此其实每个点的贡献是独立的,因此我们可以对每个点分开来考虑,而根据经典模型,每个点延申出去的路径个数的下界为 \(a_i·(sum-2\min(\lfloor\dfrac{sum}{2}\rfloor,sum-mx))\),其中 \(sum\) 为与该点相邻的边权值之和,\(mx\) 为与该点相邻的边权值的最大值。

于是现在问题转化为如何维护这东西,前面 \(a_i·sum\) 的贡献是容易维护的,这里不再赘述,难点在于如何维护 \(a_i·\min(\lfloor\dfrac{sum}{2}\rfloor,sum-mx)\) 的贡献。考虑对树进行轻重链剖分,并将待修改路径剖成 \(\log\) 段重链,对于每段重链开头和结尾的点,我们直接暴力维护删除其原来的贡献并加入新贡献,这部分维护手法较为明显,不再赘述。对于重链中间的点,相当于每次令其与父亲的边以及其与重儿子的边的权值加上 \(v\),我们需要快速维护其贡献的变化。我们将这样的节点分为三类:

  • 第一类:不存在绝对众数。
  • 第二类:存在绝对众数,且在重链上。
  • 第三类:存在绝对众数,且不在重链上。

不难发现,对于第一类节点,进行变换以后仍然是第一类节点,且其贡献恰好会增加 \(w·a_i\);对于第二类节点,进行变换以后仍然是第二类节点,且其贡献也恰好会增加 \(w·a_i\),而对于第三类节点,有的会变成 12 类节点,贡献的变化不好直接描述,否则,如果其继续保持三类节点,那么其贡献会增加 \(2w·a_i\),并且我们还可以发现,变成 12 类节点的三类点个数是均摊 \(\log n\) 的,因为每次只有切换轻重链的边缘节点才会变成三类节点,因此三类点总出现次数为 \(n\log n\)。因此我们考虑这样一个思想:对于重链中的节点,我们暴力找出所有变成 12 类节点的三类点,其余点的贡献可以用树状数组算出。怎么找出所有变成 12 类节点的三类点呢?对于一个三类点,我们记其键值为 \(2mx-sum\),那么一个三类点会变成 12 类点,当且仅当其键值 \(\le 2w\),线段树上二分即可,需要支持区间加,查询某个区间中第一个小于等于某个值的位置。

时间复杂度 \(n\log ^2n\)。值得注意的是,该算法似乎在随机树数据下跑得最慢,因此需要特判深度 \(\le 100\) 才能过。

const int MAXN = 2e5;
int n, qu, a[MAXN + 5];
int hd[MAXN + 5], to[MAXN * 2 + 5], val[MAXN * 2 + 5], nxt[MAXN * 2 + 5], ec = 0;
void adde(int u, int v, int w) {to[++ec] = v; val[ec] = w; nxt[ec] = hd[u]; hd[u] = ec;}
ll suma[MAXN + 5], SUMA = 0, SUMB = 0, sum_adj[MAXN + 5];
namespace HLD {
	int fa[MAXN + 5], siz[MAXN + 5], wson[MAXN + 5], dep[MAXN + 5];
	int top[MAXN + 5], dfn[MAXN + 5], tim, rid[MAXN + 5];
	void dfs1(int x, int f) {
		fa[x] = f; siz[x] = 1;
		for (int e = hd[x]; e; e = nxt[e]) {
			int y = to[e]; if (y == f) continue;
			dep[y] = dep[x] + 1; suma[y] = suma[x] + a[y];
			dfs1(y, x); siz[x] += siz[y];
			if (siz[y] > siz[wson[x]]) wson[x] = y;
		}
	}
	void dfs2(int x, int tp) {
		top[x] = tp; rid[dfn[x] = ++tim] = x; if (wson[x]) dfs2(wson[x], tp);
		for (int e = hd[x]; e; e = nxt[e]) if (to[e] != wson[x] && to[e] != fa[x]) dfs2(to[e], to[e]);
	}
	int getlca(int x, int y) {
		while (top[x] ^ top[y]) {
			if (dep[top[x]] < dep[top[y]]) swap(x, y);
			x = fa[top[x]];
		}
		return (dep[x] < dep[y]) ? x : y;
	}
}
using namespace HLD;
ll get_sum(int x, int y) {
	return 2 * (suma[x] + suma[y] - suma[getlca(x, y)] -
	suma[fa[getlca(x, y)]]) - a[x] - a[y];
}
namespace data_savers {
	multiset<ll> st_light[MAXN + 5];
	struct fenwick {
		ll t[MAXN + 5];
		void add(int x, ll v) {for (int i = x; i <= n; i += (i & (-i))) t[i] += v;}
		ll query(int x) {ll ret = 0; for (int i = x; i; i &= (i - 1)) ret += t[i]; return ret;}
	} T_sum, T_val, T2, T3;
	// T_sum 存储与每个点相邻所有边边权和
	// T_val 存储每个点与其父亲相连的边的边权
	// T2 存储所有二类点的点权
	void add_itvl(fenwick &T, int l, int r, ll v) {T.add(l, v); T.add(r + 1, -v);}
	ll query_itvl(fenwick &T, int l, int r) {return T.query(r) - T.query(l - 1);}
	struct segtree_solving_3 {
		struct node {int l, r; ll mn, lz;} s[MAXN * 4 + 5];
		void pushup(int k) {s[k].mn = min(s[k << 1].mn, s[k << 1 | 1].mn);}
		void build(int k, int l, int r) {
			s[k].l = l; s[k].r = r; if (l == r) return;
			int mid = l + r >> 1; build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r);
		}
		void tag(int k, ll v) {s[k].mn += v; s[k].lz += v;}
		void pushdown(int k) {if (s[k].lz) tag(k << 1, s[k].lz), tag(k << 1 | 1, s[k].lz), s[k].lz = 0;}
		void modify(int k, int l, int r, ll v) {
			if (l > r) return;
			if (l <= s[k].l && s[k].r <= r) return tag(k, v), void();
			pushdown(k); int mid = s[k].l + s[k].r >> 1;
			if (r <= mid) modify(k << 1, l, r, v);
			else if (l > mid) modify(k << 1 | 1, l, r, v);
			else modify(k << 1, l, mid, v), modify(k << 1 | 1, mid + 1, r, v);
			pushup(k);
		}
		void upd(int k, int p, ll v) {
			if (s[k].l == s[k].r) return s[k].mn = v, void();
			pushdown(k); int mid = s[k].l + s[k].r >> 1;
			if (p <= mid) upd(k << 1, p, v);
			else upd(k << 1 | 1, p, v);
			pushup(k);
		}
		int query_fst(int k, int l, int r, ll v) {
			if (s[k].mn > v || l > r) return 0;
			if (s[k].l == s[k].r) return s[k].l;
			pushdown(k); int mid = s[k].l + s[k].r >> 1;
			if (l <= s[k].l && s[k].r <= r) {
				if (s[k << 1].mn <= v) return query_fst(k << 1, l, mid, v);
				else if (s[k << 1 | 1].mn <= v) return query_fst(k << 1 | 1, mid + 1, r, v);
				else return 0;
			} else if (r <= mid) return query_fst(k << 1, l, r, v);
			else if (l > mid) return query_fst(k << 1 | 1, l, r, v);
			else {
				int p = query_fst(k << 1, l, mid, v);
				if (p) return p;
				else return query_fst(k << 1 | 1, mid + 1, r, v);
			}
		}
	} segt;
	// 对于一个 3 类点,键值为 2mx - sum
	// 对于 12 类点,键值为 inf
	// 每次暴力改键值 <2w 的点,对于 >2w 的执行区间减。 
	int gettyp(int x) {
		ll mx = (st_light[x].empty()) ? 0 : (*st_light[x].rbegin());
		chkmax(mx, T_val.query(dfn[x]));
		if (wson[x]) chkmax(mx, T_val.query(dfn[wson[x]]));
		if (mx * 2 <= T_sum.query(dfn[x])) return 1;
		else if (mx == T_val.query(dfn[x]) || (wson[x] && mx == T_val.query(dfn[wson[x]])))
			return 2;
		else return 3;
	}
	ll calc(int x) {
//		printf("calc %d\n", x);
		ll mx = (st_light[x].empty()) ? 0 : (*st_light[x].rbegin());
		chkmax(mx, T_val.query(dfn[x]));
		if (wson[x]) chkmax(mx, T_val.query(dfn[wson[x]]));
		ll ss = T_sum.query(dfn[x]);
		return a[x] * min(ss / 2, ss - mx);
	}
	void ins(int x, int coef) {
		int tp = gettyp(x);
		if (tp <= 2) {
			T2.add(dfn[x], coef * a[x]);
			if (coef != -1) segt.upd(1, dfn[x], 1e18);
		} else {
			if (coef != -1)
				segt.upd(1, dfn[x], 2 * (*st_light[x].rbegin()) - T_sum.query(dfn[x]));
			T3.add(dfn[x], 2 * coef * a[x]);
		}
	}
	void init() {
		for (int i = 1; i <= n; i++) for (int e = hd[i]; e; e = nxt[e])
			if (to[e] != fa[i] && to[e] != wson[i])
				st_light[i].insert(val[e]);
		for (int i = 1; i <= n; i++) add_itvl(T_sum, dfn[i], dfn[i], sum_adj[i]);
		for (int i = 2; i <= n; i++) for (int e = hd[i]; e; e = nxt[e])
			if (to[e] == fa[i]) add_itvl(T_val, dfn[i], dfn[i], val[e]);
		for (int i = 1; i <= n; i++) SUMB += calc(i);
		segt.build(1, 1, n);
		for (int i = 1; i <= n; i++) ins(i, 1);
	}
}
using namespace data_savers;
int stk3[MAXN + 5], tp = 0;
void deal(int l, int r, int w) {
	if (l > r) return;
	SUMB += 1ll * w * query_itvl(T2, l, r);
	int pre = l - 1;
	while (1) {
		int pos = segt.query_fst(1, pre + 1, r, 2 * w);
		if (!pos) break;
		stk3[++tp] = rid[pos]; pre = pos;
	}
}
bool FLG = 1;
void modify(int u, int v, int w) {
	int x, y;
	// 撤销原来的贡献
	if (FLG) {
		x = u, y = v;
		while (dep[x] > dep[y]) SUMB -= calc(x), x = fa[x];
		while (dep[y] > dep[x]) SUMB -= calc(y), y = fa[y];
		while (x ^ y) SUMB -= calc(x), SUMB -= calc(y), x = fa[x], y = fa[y];
		SUMB -= calc(x);
	} else {
		x = u, y = v; tp = 0;
		while (top[x] ^ top[y]) {
			if (dep[top[x]] < dep[top[y]]) swap(x, y);
			SUMB -= calc(x); ins(x, -1);
			deal(dfn[top[x]], dfn[x] - 1, w);
			x = fa[top[x]];
		}
		if (dep[x] < dep[y]) swap(x, y);
		SUMB -= calc(x); ins(x, -1);
		if (x != y) {
			deal(dfn[y] + 1, dfn[x] - 1, w);
			SUMB -= calc(y);
			ins(y, -1);
		}
		for (int i = 1; i <= tp; i++) SUMB -= calc(stk3[i]), ins(stk3[i], -1);
	}
	// 修改 
	x = u; y = v;
	while (top[x] ^ top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		st_light[fa[top[x]]].erase(st_light[fa[top[x]]].find(T_val.query(dfn[top[x]])));
		add_itvl(T_sum, dfn[top[x]], dfn[x], 2 * w);
		add_itvl(T_val, dfn[top[x]], dfn[x], w);
		st_light[fa[top[x]]].insert(T_val.query(dfn[top[x]]));
		x = fa[top[x]];
	}
	if (dep[x] < dep[y]) swap(x, y);
	add_itvl(T_sum, dfn[y], dfn[x], 2 * w);
	add_itvl(T_val, dfn[y] + 1, dfn[x], w);
	add_itvl(T_sum, dfn[u], dfn[u], -w);
	add_itvl(T_sum, dfn[v], dfn[v], -w);
	// 加入新贡献
	if (FLG) {
		x = u, y = v;
		while (dep[x] > dep[y]) SUMB += calc(x), x = fa[x];
		while (dep[y] > dep[x]) SUMB += calc(y), y = fa[y];
		while (x ^ y) SUMB += calc(x), SUMB += calc(y), x = fa[x], y = fa[y];
		SUMB += calc(x);
	} else {
		x = u; y = v;
		while (top[x] ^ top[y]) {
			if (dep[top[x]] < dep[top[y]]) swap(x, y);
			SUMB += calc(x); ins(x, 1); x = fa[top[x]];
		}
		if (dep[x] < dep[y]) swap(x, y);
		SUMB += calc(x); ins(x, 1);
		if (x != y) {
			SUMB += calc(y);
			ins(y, 1);
		}
		for (int i = 1; i <= tp; i++) SUMB += calc(stk3[i]), ins(stk3[i], 1);
		// 修改 3 类点 -> 3 类点的贡献
		x = u, y = v;
		while (top[x] ^ top[y]) {
			if (dep[top[x]] < dep[top[y]]) swap(x, y);
			segt.modify(1, dfn[top[x]], dfn[x] - 1, -2 * w);
			SUMB += w * query_itvl(T3, dfn[top[x]], dfn[x] - 1);
			x = fa[top[x]];
		}
		if (dep[x] < dep[y]) swap(x, y);
		if (x != y) {
			SUMB += w * query_itvl(T3, dfn[y] + 1, dfn[x] - 1);
			segt.modify(1, dfn[y] + 1, dfn[x] - 1, -2 * w);
		}
	}
}
int main() {
	freopen("snow.in", "r", stdin);
	freopen("snow.out", "w", stdout);
	scanf("%d%d", &n, &qu);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1, u, v, w; i < n; i++) {
		scanf("%d%d%d", &u, &v, &w);
		adde(u, v, w); adde(v, u, w);
		SUMA += 1ll * (a[u] + a[v]) * w;
		sum_adj[u] += w; sum_adj[v] += w;
	}
	suma[1] = a[1]; dfs1(1, 0); dfs2(1, 1); init();
	for (int i = 1; i <= n; i++) FLG &= (dep[i] <= 100);
	printf("%lld\n", SUMA - 2 * SUMB);
	while (qu--) {
		int x, y, z; scanf("%d%d%d", &x, &y, &z);
		SUMA += 1ll * z * get_sum(x, y);
		modify(x, y, z);
		printf("%lld\n", SUMA - 2 * SUMB);
	}
	return 0;
}

标签:剖分,NFLSOJ,fa,int,top,模拟题,dep,MAXN,SUMB
来源: https://www.cnblogs.com/ET2006/p/NFLSOJ-10267.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有