时代的眼泪(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;
}
思维方式
假若要统计子树内的信息,若答案具有可减性,考虑进入时求一遍答案,出去时求一遍答案,相减即为所得。