西瓜树之二

题目描述

给定一棵 \(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;
}
上一篇:canvas/CSS仪表盘效果


下一篇:快速读入数字字符串函数