题目描述
给定一棵 \(N\) 个节点的西瓜树,第 \(i\) 个点的编号为 \(i\),第\(j\)条边的编号为\(j\)。
有\(Q\)次查询,每次给出两个整数\(l,r\),查询如果只保留树上点编号在\([l,r]\)内的点,边编号在\([l,r]\)内的边,还剩下多少点连通块。
提示,给出 \(l,r\) 后,此时点 \(a\) 与 \(b\) 连通等价于 \(l\le a,b\le r\)且 \(a\) 到 \(b\) 在树上的简单路径中所有点与边编号都在 \([l,r]\) 之间。
\(N,Q\le 10^6\)
解法
一道树状数组的题,鬼知道这个华强专题的测试为什么那么喜欢考树(话说华强的西瓜为什么会长在树上,以及萨日朗到底是什么梗)。
询问之间互相独立且不带修改,考虑离线。
首先有一个结论就是,假如对于一堆树上的节点,每在它们任意两个之间连一条边,连通块就会减少一个,原因很简单。树的特性决定了任意一条边都只会连接两个不同的连通块(不然就会出现环,那就不是树了,毕竟西瓜树它也是树啊)。
于是做如下考虑:假如只保留点不保留边,那么会得到 \(r-l+1\) 个连通块;接下来考虑所有编号在\([l,r]\)中的边有多少条连接的两个点都是在 \([l,r]\) 的区间内。
可以给予每条边三个参数 \(id,x,y\) ,问题就转化成了有多少条边的三个参数都是在 \([l,r]\) 里的。进而把每条边的参数优化成 \(max,min\),问题就变成了有多少点的两个参数都是在 \([l,r]\) 内的,于是问题就转化成了平面上矩形内点数的问题。
树状数组离线处理即可。
#include<cstdio>
#include<algorithm>
//#define zczc
using namespace std;
const int N=1000010;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
wh*=f;return;
}
inline int min(int s1,int s2){
return s1<s2?s1:s2;
}
inline int max(int s1,int s2){
return s1<s2?s2:s1;
}
int m,n,s[N],an[N];
namespace g{
struct no{
int x,y,pl;
bool change,add;
}b[N*6];
inline bool cmp(no s1,no s2){
if(s1.x==s2.x){
if(s1.y==s2.y)return s1.change;
else return s1.y<s2.y;
}
else return s1.x<s2.x;
}
int cnt;
#define lowbit (wh&-wh)
int t[N];
void change(int wh){
for(;wh<=m;wh+=lowbit)t[wh]++;
return;
}
int work(int wh){
int an=0;
for(;wh;wh-=lowbit)an+=t[wh];
return an;
}
#undef lowbit
void add(int pl,int s1,int s2,bool change,bool add){
cnt++;
b[cnt].pl=pl;
b[cnt].x=s1;
b[cnt].y=s2;
b[cnt].change=change;
b[cnt].add=add;
return;
}
void solve(){
sort(b+1,b+cnt+1,cmp);
for(int i=1;i<=cnt;i++){
if(b[i].change){
change(b[i].y);
continue;
}
int now=work(b[i].y);
an[b[i].pl]+=now*(b[i].add?1:-1);
}
return;
}
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
int s1,s2;
read(m);read(n);
for(int i=1;i<m;i++){
read(s1);read(s2);
g::add(0,min(min(s1,s2),i),max(max(s1,s2),i),true,false);
}
for(int i=1;i<=n;i++){
read(s1);read(s2);
s[i]=s2-s1+1;
g::add(i,s2,s2,false,true);
g::add(i,s1-1,s1-1,false,true);
g::add(i,s1-1,s2,false,false);
g::add(i,s2,s1-1,false,false);
}
g::solve();
for(int i=1;i<=n;i++)printf("%d\n",s[i]-an[i]);
return 0;
}