@atcoder


@description@

给定一个 N 个点的树,第 i 条边连接 ai 与 bi,颜色为 ci,边权为 di。

现有 Q 个询问,每次询问假设将颜色为 xi 的边的边权全部修改为 yi 时,ui 到 vi 这一条路径上的边权和。

Constraints
2≤N≤10^5, 1≤Q≤10^5
1≤ai,bi,uj,vj≤N
1≤ci,xj≤N−1
1≤di,yj≤10^4
保证给出的图是树。保证所有输入值为整数。

Input
如下:
N Q
a1 b1 c1 d1
:
aN−1 bN-1 cN−1 dN−1
x1 y1 u1 v1
:
xQ yQ uQ vQ

Output
输出 Q 行,每行输出对应询问的答案。

Sample Input 1
5 3
1 2 1 10
1 3 2 20
2 4 4 30
5 2 1 40
1 100 1 4
1 100 1 5
3 1000 3 4
**Sample Output 1 **
130
200
60

@solution - 1@

跟颜色有关,虽然题目中说是要“修改”,但实际上每一个询问之间是独立的,即修改不会对后面的询问产生影响。
所以不难想到可以使用树上莫队。

首先将边的颜色与边权下放至点。然后莫队时,统计出区间内每个点的点权(就是下放的边权)和 S、区间每种颜色的出现次数 cnt[i]、区间每种颜色的边权和 s[i]。
查询时答案即为 S - s[x] + y*cnt[x]。

有关树上莫队的实现方法,你可以在yhn学长的这篇博客里找到详情。

稍微简述一下树上莫队吧,就当作复习。
树上莫队虽然有直接树上分块的方法,但是实现起来较为麻烦,所以一般来讲会写将树转为括号序列然后再进行线性序列上的莫队。但是这个方法因为要将树转为括号序列,所以常数要翻一倍,对于有些题目可能会比较卡常。

转为括号序列怎么提取一条链的信息呢?注意我们的莫队算法是一个一个元素加入而不是整体提取区间信息,所以我们可以使某个区间内,有用的点出现奇数次,无用的点出现偶数次。然后每一次元素是加入还是删除就观察它的出现次数而决定。

令 fir[x] 表示 x 对应的左括号,bac[x] 表示 x 对应的右括号。讨论一下 u, v 是否为祖先关系:
如果是,不妨假设 u 是 v 的祖先。则我们需要的区间即 [fir[u], fir[v]]。
如果不是,不妨假设 u 在 v 之前出现。则我们需要的区间即 [bac[u], fir[v]]。注意这种情况需要特殊处理 u, v 的 lca。
实现时可以通过某些技巧避免 lca 的 问题。

@accepted code - 1@

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 100000;
const int BLOCK = 450;
struct edge{
	int to, dis, clr;
	edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt = &edges[0];
void addedge(int u, int v, int c, int d) {
	edge *p = (++ecnt);
	p->to = v, p->clr = c, p->dis = d;
	p->nxt = adj[u], adj[u] = p;
	p = (++ecnt);
	p->to = u, p->clr = c, p->dis = d;
	p->nxt = adj[v], adj[v] = p;
}
int clr[MAXN + 5], dis[MAXN + 5];
int fa[MAXN + 5][20], dep[MAXN + 5];
int dfn[2*MAXN + 5], fir[MAXN + 5], bac[MAXN + 5], dcnt = 0;
void dfs(int x, int pre) {
	dfn[++dcnt] = x, fir[x] = dcnt;
	fa[x][0] = pre, dep[x] = dep[pre] + 1;
	for(int i=1;i<20;i++)
		fa[x][i] = fa[fa[x][i-1]][i-1];
	for(edge *p=adj[x];p;p=p->nxt)
		if( p->to != pre ) {
			dfs(p->to, x);
			clr[p->to] = p->clr, dis[p->to] = p->dis;
		}
	dfn[++dcnt] = x, bac[x] = dcnt;
}
int lca(int u, int v) {
	if( dep[u] < dep[v] ) swap(u, v);
	for(int i=19;i>=0;i--)
		if( dep[fa[u][i]] >= dep[v] )
			u = fa[u][i];
	if( u == v ) return u;
	for(int i=19;i>=0;i--)
		if( fa[u][i] != fa[v][i] )
			u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}
struct query{
	int le, ri, num;
	int x, y;
}qry[MAXN + 5];
bool operator < (query a, query b) {
	return (a.le/BLOCK == b.le/BLOCK) ? a.ri < b.ri : a.le < b.le;
}
int nwres, cnt[MAXN + 5], tag[MAXN + 5];
int sum[MAXN + 5], ans[MAXN + 5];
void update(int x) {
	int y = dfn[x];
	if( tag[y] )
		cnt[clr[y]]--, sum[clr[y]] -= dis[y], nwres -= dis[y];
	else cnt[clr[y]]++, sum[clr[y]] += dis[y], nwres += dis[y];
	tag[y] ^= 1;
}
int main() {
	int N, Q; scanf("%d%d", &N, &Q);
	for(int i=1;i<N;i++) {
		int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d);
		addedge(a, b, c, d);
	}
	dfs(1, 0);
	for(int i=1;i<=Q;i++) {
		int x, y, u, v; scanf("%d%d%d%d", &x, &y, &u, &v);
		int l = lca(u, v);
		if( l == u || l == v ) {
			if( l == v ) swap(u, v);
			qry[i].le = fir[u] + 1, qry[i].ri = fir[v];
		}
		else {
			if( fir[u] > fir[v] ) swap(u, v);
			qry[i].le = bac[u], qry[i].ri = fir[v];
		}
		qry[i].num = i, qry[i].x = x, qry[i].y = y;
	}
	sort(qry + 1, qry + Q + 1);
	int l = 1, r = 0;
	for(int i=1;i<=Q;i++) {
		while( l > qry[i].le ) update(--l);
		while( r < qry[i].ri ) update(++r);
		while( l < qry[i].le ) update(l++);
		while( r > qry[i].ri ) update(r--);
		ans[qry[i].num] = nwres - sum[qry[i].x] + cnt[qry[i].x]*qry[i].y;
	}
	for(int i=1;i<=Q;i++)
		printf("%d
", ans[i]);
}

@solution - 2@

你说要强制在线其实也可以,而且复杂度可能还比莫队优秀(只是不方便直接粘贴模板)

首先我们可以通过简单的差分将问题拆成若干点到根的链询问,然后跟莫队一样考虑求某种颜色的出现次数以及权值和。
注意从某个点的父亲到这个点时最多只会加入一种颜色的一条边,所以我们可以写一个可持久化数组(从实现的功能来说它的确是可持久化数组,但从实现的方式来说你只能写可持久化线段树)。

然后从父亲的版本通过加一条父亲到自己的边的信息得到当前点的版本。
查询时直接查询,没什么问题。

@accepted code - 2@

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 100000;
typedef pair<int, int> pii;
struct segtree{
	struct node{
		int cnt, sum;
		node *ch[2];
	}pl[20*MAXN + 5], *NIL, *ncnt;
	segtree(){NIL = ncnt = &pl[0]; NIL->ch[0] = NIL->ch[1] = NIL;}
	node *add(node *pre, int l, int r, int pos, int del) {
		node *nw = (++ncnt); (*nw) = (*pre);
		if( l == r ) {
			nw->cnt++, nw->sum += del;
			return nw;
		}
		int mid = (l + r) >> 1;
		if( pos <= mid ) nw->ch[0] = add(pre->ch[0], l, mid, pos, del);
		else nw->ch[1] = add(pre->ch[1], mid + 1, r, pos, del);
		return nw;
	}
	pii query(node *nw, int l, int r, int pos) {
		if( l == r )
			return make_pair(nw->cnt, nw->sum);
		int mid = (l + r) >> 1;
		if( pos <= mid ) return query(nw->ch[0], l, mid, pos);
		else return query(nw->ch[1], mid + 1, r, pos);
	}
}T;
segtree::node *rt[MAXN + 5];
struct edge{
	int dis, clr, to;
	edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v, int c, int d) {
	edge *p = (++ecnt);
	p->to = v, p->clr = c, p->dis = d;
	p->nxt = adj[u], adj[u] = p;
	p = (++ecnt);
	p->to = u, p->clr = c, p->dis = d;
	p->nxt = adj[v], adj[v] = p;
}
int N, Q;
int fa[20][MAXN + 5], dep[MAXN + 5];
int dis[MAXN + 5];
void dfs(int x, int f) {
	fa[0][x] = f;
	for(int i=1;i<20;i++)
		fa[i][x] = fa[i-1][fa[i-1][x]];
	dep[x] = dep[f] + 1;
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == f ) continue;
		rt[p->to] = T.add(rt[x], 1, N-1, p->clr, p->dis);
		dis[p->to] = dis[x] + p->dis;
		dfs(p->to, x);
	}
}
int lca(int u, int v) {
	if( dep[u] < dep[v] ) swap(u, v);
	for(int i=19;i>=0;i--)
		if( dep[fa[i][u]] >= dep[v] )
			u = fa[i][u];
	if( u == v ) return u;
	for(int i=19;i>=0;i--)
		if( fa[i][u] != fa[i][v] )
			u = fa[i][u], v = fa[i][v];
	return fa[0][u];
}
int main() {
	scanf("%d%d", &N, &Q);
	for(int i=1;i<N;i++) {
		int a, b, c, d; scanf("%d%d%d%d", &a, &b, &c, &d);
		addedge(a, b, c, d);
	}
	rt[1] = T.NIL, dfs(1, 0);
	for(int i=1;i<=Q;i++) {
		int x, y, u, v; scanf("%d%d%d%d", &x, &y, &u, &v);
		int p = lca(u, v);
		pii a = T.query(rt[u], 1, N-1, x), b = T.query(rt[v], 1, N-1, x), c = T.query(rt[p], 1, N-1, x);
		printf("%d
", (dis[u]+dis[v]-2*dis[p]) - (a.second+b.second-2*c.second) + (a.first+b.first-2*c.first)*y);
	}
}

@details@

他们在说这道题很难。。。

我觉得是因为他们没学过莫队(还有可持久化)的原因吧。。。

话说 ABC 什么时候把难度加到这种程度了。。。我记得以前的 ABC不是全是简单模拟简单dp简单贪心嘛。。。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11148818.html