时代的眼泪(20230305 C组 T2)

题意简述

给定一棵树,每个节点上有一个权值 \(w_i\)。若当前节点为 \(r\),则每个点 \(p\) 的贡献是 \(p\)\(r\) 路径上权值比 \(w_p\) 大的点的数量(包括 \(r\)),答案就是所有贡献的和。给出若干个询问 \(u\),回答以 \(u\) 为根节点时的答案。

形式化地,就是求:

\[\sum_{v\neq u}{\sum_{x\ on\ path(u,v)}{\big[w_x>w_u\big]}} \]

节点个数小于等于 \(10^6\),询问次数小于等于 \(10^6\)。时限 2s。

解题思路

考虑换根预处理答案。先以 \(1\) 为根节点建树。若父亲节点答案是 \(ans_{fa}\),全局中比当前节点小的点个数为 \(sm_u\),当前节点的子树(当然包括他自己)中比父亲节点小的个数是 \(ffa_u\),比它自己小的个数是 \(fson_u\)。那么当前节点答案是:

\[ans_u=ans_{fa}+sm_u-ffa_u-fson_u \]

\(sm_u\) 很好维护,排个序就可以了。但是 \(ffa\)\(fson\) 似乎不好解决。

实际上,我们可以开一个基于值域的树状数组,并且对 \(w_i\) 离散化。显然通过这个我们可以 \(O(\log n)\) 得到任意时刻比 \(w_i\) 小的数量。

在 dfs 的过程中,进入节点 \(u\) 时,向树状数组内插入 \(w_u\)。统计一遍当前比 \(w_u\) 小的数的个数和比 \(w_{fa}\) 小的个数,记为 \(now_u\)\(now_{fa}\)。回溯到当前节点时,再次统计,分别记为 \(new_u\)\(new_{fa}\)。出来的时候和进去的时候的差异就是子树!的那么由于数量的可减性:

\[fson=new_u-now_u\quad ffa=new_{fa}-now_{fa} \]

问题到这里就解决了。注意 \(ans_1=\sum fson\)

解题代码

#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define R myio::read_int()
//这里就把快读快写省掉了
using namespace std;
const int N=1e6+5;
int n,Q;long long ans[N];

int tot,w[N],head[N];
struct edge{
	int v,nxt;
	edge(int _v,int _nxt):v(_v),nxt(_nxt){}
	edge() {}
} e[N<<1];
void adde(int u,int v) {e[++tot].v=v,e[tot].nxt=head[u],head[u]=tot;}

int c[N]; 
void upd(int x) {for(;x<=n;x+=x&-x) ++c[x];}
int ask(int x) {int Sum=0;for(;x;x-=x&-x) Sum+=c[x];return Sum;}

int ys[N];
void read_map() {
	n=R,Q=R;w[0]=n+2;
	for(int i=1;i<=n;++i) w[i]=ys[i]=R;
	for(int i=1,u,v;i<n;++i) {
		u=R,v=R;
		adde(u,v);adde(v,u);
	}
}

int fson[N],ffa[N];
void dfs(int rt,int fa) {
	upd(w[rt]);
	int nows=ask(w[rt]-1),nowf=ask(w[fa]-1);
	for(int i=head[rt];i;i=e[i].nxt) {
		int to=e[i].v;
		if(to!=fa) dfs(to,rt);
	} int news=ask(w[rt]-1),newf=ask(w[fa]-1);
	fson[rt]=news-nows,ffa[rt]=newf-nowf;
}

int smu[N];
void dfs2(int rt,int fa) {
	ans[rt]=ans[fa]+smu[rt]-fson[rt]-ffa[rt];
	if(w[rt]<w[fa]) ans[rt]--;
	for(int i=head[rt];i;i=e[i].nxt) {
		int to=e[i].v;
		if(to!=fa) dfs2(to,rt);
	}
}

void pretreatment() {
	sort(ys+1,ys+1+n);
	for(int i=1;i<=n;++i) smu[i]=lower_bound(ys+1,ys+1+n,w[i])-ys-1;
	int nend=unique(ys+1,ys+1+n)-ys-1;
	for(int i=1;i<=n;++i) w[i]=lower_bound(ys+1,ys+1+nend,w[i])-ys;
	dfs(1,0);
	for(int i=1;i<=n;++i) ans[1]+=fson[i];
	for(int i=head[1];i;i=e[i].nxt) {
		int to=e[i].v;
		dfs2(to,1);
	}
}

void solve() {
	pretreatment();
	while(Q--) {
		int u=R;
		myio::print_int(ans[u]);
	}
}

signed main(){
	read_map();
	solve();
	return 0;
}

思维方式

假若要统计子树内的信息,若答案具有可减性,考虑进入时求一遍答案,出去时求一遍答案,相减即为所得。

posted @ 2023-03-14 08:31  robinyqc  阅读(19)  评论(0编辑  收藏  举报