时代的眼泪(20230305 C组 T2)

题意简述

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

形式化地,就是求:

vux on path(u,v)[wx>wu]

节点个数小于等于 106,询问次数小于等于 106。时限 2s。

解题思路

考虑换根预处理答案。先以 1 为根节点建树。若父亲节点答案是 ansfa,全局中比当前节点小的点个数为 smu,当前节点的子树(当然包括他自己)中比父亲节点小的个数是 ffau,比它自己小的个数是 fsonu。那么当前节点答案是:

ansu=ansfa+smuffaufsonu

smu 很好维护,排个序就可以了。但是 ffafson 似乎不好解决。

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

在 dfs 的过程中,进入节点 u 时,向树状数组内插入 wu。统计一遍当前比 wu 小的数的个数和比 wfa 小的个数,记为 nowunowfa。回溯到当前节点时,再次统计,分别记为 newunewfa。出来的时候和进去的时候的差异就是子树!的那么由于数量的可减性:

fson=newunowuffa=newfanowfa

问题到这里就解决了。注意 ans1=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 @   robinyqc  阅读(22)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示