bzoj 4012. [HNOI2015]开店(动态点分治)

在这里插入图片描述


先考虑在点分树上如何处理一个弱化的问题:查询其它点到 u u u 点的距离之和。
在点分树上每个节点维护子树到它的距离和,为了方便向上合并其他节点的贡献,再维护每个节点到其父亲节点的距离和,每到一个父亲减一下就得到除这棵子树外其它点到父亲节点的距离和,再加上其到父亲节点的这条边的贡献即可。
维护三个数组: s u m a [ v ] , s u m b [ v ] , s i z [ v ] suma[v],sumb[v],siz[v] suma[v],sumb[v],siz[v] 分别表示子树到 v v v 的距离和,子树到 v v v 的父亲节点的距离和,子树内的节点个数。每向跳一个节点更新答案:初始 a n s = s u m a [ x ] ans = suma[x] ans=suma[x](x是查询节点), a n s = a n s + s u m a [ f a [ v ] ] − s u m b [ v ] + ( s z [ f a [ v ] ] − s z [ v ] ) ∗ d i s ( x , f a [ v ] ) ans = ans + suma[fa[v]] - sumb[v] +(sz[fa[v]] - sz[v]) * dis(x,fa[v]) ans=ans+suma[fa[v]]sumb[v]+(sz[fa[v]]sz[v])dis(x,fa[v])

考虑年龄要在范围 [ L , R ] [L,R] [L,R] 内:
建立点分树,考虑在每个点维护两棵线段树,分别统计子树内年龄为 p p p 的妖怪距离 u u u 的距离和 以及距离 u u u 的父亲的距离和。查询答案暴力向上跳合并。

由于只有询问没有修改操作,上动态开点线段树属实没必要,考虑将妖怪以 (年龄,距离) 的 pair 形式存在每个点 vector 中,并排序求前缀和,查询时二分一下找到 [L,R] 这个边界即可。

注意要开 long long
代码垃圾,只好开氧气极致优化


代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5e4 + 100;
typedef long long ll;
#define pii pair<int,ll>
#define fir first
#define sec second
#define lowbit(i) (i & (-i))
struct Graph {
	int head[maxn], to[maxn << 1], cnt, nxt[maxn << 1], w[maxn << 1];
	void init() {
		memset(head,-1,sizeof head);
		cnt = 0;
	}
	void add(int u,int v,int c) {
		to[cnt] = v;
		nxt[cnt] = head[u];
		w[cnt] = c;
		head[u] = cnt++;
		
		to[cnt] = u;
		nxt[cnt] = head[v];
		w[cnt] = c;
		head[v] = cnt++;
	}
}G;
vector<pii> val[maxn],fval[maxn];					//每个点开桶维护妖怪的贡献 
int st[maxn * 5][30],d[maxn],cnt,a[maxn];
int fir[maxn],vis[maxn],f[maxn],root,sz[maxn],siz,p[maxn];
int n,q,RT,A;
inline int read()
{
	int x=0,f=1;char ch;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void dfs1(int u,int fa) {			//预处理深度,st表,以及欧拉序 
	fir[u] = ++cnt; st[cnt][0] = u;
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i];
		if (v == fa) continue;
		d[v] = d[u] + G.w[i];
		dfs1(v,u);
		st[++cnt][0] = u;
	}
}
void dfs2(int u,int fa) {			//求一棵子树的重心 
	sz[u] = 1; f[u] = 0;
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i];
		if (v == fa || vis[v]) continue;
		dfs2(v,u);
		sz[u] += sz[v];
		if (sz[v] > f[u]) f[u] = sz[v];
	}
	if (siz - sz[u] > f[u]) f[u] = siz - sz[u];
	if (!root || f[u] < f[root]) root = u;
}
void dfs3(int u,int fa) { 			//点分构建点分树 
	vis[u] = 1;
	int all = siz;
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i];
		if (v == fa || vis[v]) continue;
		root = 0, siz = sz[v]; 
		if (siz > sz[u]) siz = all - sz[u];
		dfs2(v,u);
		p[root] = u;	//构建点分树
		dfs3(root,u); 
	}
}
int cal(int u,int v) {
	return d[u] < d[v] ? u : v;
}
int getlca(int x,int y) {
	if (fir[x] > fir[y]) swap(x,y);
	int p = log2(fir[y] - fir[x] + 1);
	return cal(st[fir[x]][p],st[fir[y] - (1 << p) + 1][p]);
}
int getdis(int x,int y) {
	return d[x] + d[y] - 2 * d[getlca(x,y)];
}
void prework() {
	dfs1(1,0);
	for (int i = 1; i <= log2(cnt); i++)
		for (int j = 1; j + (1 << i) - 1 <= cnt; j++)
			st[j][i] = cal(st[j][i - 1],st[j + (1 << (i - 1))][i - 1]);
	root = 0, siz = n; dfs2(1,0);
	RT = root; dfs3(root,0);		//保留第一次的根节点,并构建点分树 
}
void update(int x,int y) {	//x点插入一个年龄为 y 的妖怪 
	for (int i = x; i; i = p[i]) {
		int dist = getdis(x,i);
		val[i].push_back(pii(y,dist));
		if (p[i]) fval[i].push_back(pii(y,getdis(x,p[i])));
	}
}
ll qry(int u,int L,int R) {
	int l = lower_bound(val[u].begin(),val[u].end(),pii(L,-0x3f3f3f3f)) - val[u].begin();
	int r = upper_bound(val[u].begin(),val[u].end(),pii(R + 1,-0x3f3f3f3f)) - val[u].begin();
	ll tmp = (r > 0 ? val[u][r - 1].sec : 0) - (l > 0 ? val[u][l - 1].sec : 0);
	for (int i = u; p[i]; i = p[i]) {
		int l = lower_bound(val[p[i]].begin(),val[p[i]].end(),pii(L,-0x3f3f3f3f)) - val[p[i]].begin();
		int r = upper_bound(val[p[i]].begin(),val[p[i]].end(),pii(R + 1,-0x3f3f3f3f)) - val[p[i]].begin();
		ll res = (r > 0 ? val[p[i]][r - 1].sec : 0) - (l > 0 ? val[p[i]][l - 1].sec : 0);
		
		int li = lower_bound(fval[i].begin(),fval[i].end(),pii(L,-0x3f3f3f3f)) - fval[i].begin();
		int ri = upper_bound(fval[i].begin(),fval[i].end(),pii(R + 1,-0x3f3f3f3f)) - fval[i].begin();
		res -= (ri > 0 ? fval[i][ri - 1].sec : 0) - (li > 0 ? fval[i][li - 1].sec : 0);
		
		int dist = getdis(u,p[i]);
		int num = (r - l) - (ri - li);
		tmp = tmp + res + 1ll * num * dist;
	}
	return tmp;
}
int main() {
	n = read(); q = read(); A = read();
	G.init();
	for (int i = 1; i <= n; i++) {
		a[i] = read();
	}
	for (int i = 1; i < n; i++) {
		int u,v,w; u = read(); v = read(); w = read();
		G.add(u,v,w);
	}
	prework();
	for (int i = 1; i <= n; i++)						//复杂度 nlogn 
		update(i,a[i]);
	for (int i = 1; i <= n; i++) {						//预处理,复杂度 nlog^2n 
		sort(val[i].begin(),val[i].end());
		sort(fval[i].begin(),fval[i].end());
		for (int j = 1; j < val[i].size(); j++) 
			val[i][j].sec += val[i][j - 1].sec;
		for (int j = 1; j < fval[i].size(); j++)
			fval[i][j].sec += fval[i][j - 1].sec;
	}
	ll ans = 0;
	while (q--) {
		int u,a,b; u = read(); a = read(); b = read();
		a = (a + ans) % A, b = (b + ans) % A;
		int L = min(a,b), R = max(a,b);
		printf("%lld\n",ans = qry(u,L,R));
		cnt++;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值