【2018提高测试】飘雪圣域 【连通性+可持久化线段树 / 离线+权值线段树】 (详解)

描述
​ IcePrincess_1968 和 IcePrince_1968 长大了,他们开始协助国王 IceKing_1968 管理国内事物。

​ IcePrincess_1968 和 IcePrince_1968 住在一个宁静悠远的王国:IceKingdom —— 飘雪圣域。飘雪圣域有 n 个城镇,编号 1,2,3…n有些城镇之间有道路,且满足任意两点之间有且仅有一条路径。飘雪圣域风景优美,但气候并不是太好。根据 IcePrince_1968 的气候探测仪,将来会发生 qq 场暴风雪。每场暴风雪可以用两个整数 li,ri刻画,表示这场暴风雪之后,只有编号属于[li,ri]的城市没有受到暴风雪的影响。

​ 在暴风雪的影响下迅速确定王国的农业生产方案是非常重要的事情。IceKing_1968 认为,一个农业生产地域应该是一个极大连通块,满足每个节点都没有被暴风雪影响。这里极大连通块的定义是:不存在一个不属于该点集的未被暴风雪影响的点与该连通块连通。

​ IcePrincess_1968 要负责算出每次暴风雪后,王国能拥有多少个农业生产地域。注意这里每次暴风雪是独立的,即每次暴风雪过后,直到每个城镇重新焕发生机,下一次暴风雪才会到来。

​ 正如上文所述,IcePrincess_1968 擅长文学但不擅长计算机,于是请你帮忙。

输入
第一行包含两个正整数 n,q,表示 IceKingdom 的城镇个数和暴风雪次数。

​ 第 2 至第 n 行,每行两个正整数 x,y,表示城镇 x 和城镇 y 之间有一条道路。

​ 第 n+1至第 n+q行,每行两个正整数 li,ri,描述一场暴风雪,含义如题面所述。

输出
输出文件共有 q 行,第 ii 行表示在第 ii 场暴风雪之后农业生产地域的个数。

样例输入 [复制]
4 3
1 2
2 3
2 4
1 2
1 3
3 4
样例输出 [复制]
1
1
2
提示
【输入输出样例 1 解释】

​ 第一次询问,只有(1,2)一个连通块。

​ 第二次询问,只有(1,2,3)一个连通块。

​ 第三次询问,有 3 和 4 两个连通块。

【数据规模】

对于 30%的数据:n<=100,q<=100

对于 50%的数据:n<=2,000,q<=2,000

对于 100%的数据:n<=200,000,q<=200,000对于所有的暴风雪,li<=ri。

思路:

第一个突破口:对于一次修改,如果只枚举点,那么无法直接判断连通块的增减
改为枚举边: 删除一条边,多一个连通块。

但如果只删除与[l,r]区间内的点相关的点,我们不知道形成的连通块有哪些是[l,r]中的。所以这里要有一定的容斥思想:只要不是 [l,r]内部的边都删除

最后:所有不在[l,r]内的点都被单独剖开(一定会的),独自作为连通块,加上 在范围内的点组成的连通块,
用这个总数减去不在范围内的点的个数即可。

那么问题变成了,log以内询问删除多少边
把删点转化成删边:

对于一条边,把 起点和终点处理出来 ,由于顺序无影响,讨论的时强制让起点编号小于终点
对于 边 i: 如果 u<l | | v>r 则删除,
但 " | | " 会导致重复讨论,
转化成: u<l&&v<=n 和 u>=l&&v>r (包含所有情况)

在坐标系中画出更直观 ,一条边相当于 坐标为(u,v) 的点

在这里插入图片描述

对于在线,可以用可持久化权值线段树查询 (按照u排序,u作为插入时间指标,插入v, 区间就是u的限制,再查询v)

对于离线,用权值树状数组/线段树实现
可以把区间询问按照 两个端点分别为 一、二关键字排序

这里要注意, u和l有关,所以 以u为关键字要 排序l r和v有关系, 同理, 以v关键字排序 r
如果离线的话,有两个等式,要统计两次答案,不方便 ,转化成 (r-l+1)- (“”好边“总数) ,即 上述等式的补集

在这里插入图片描述

这里放主席树的代码:

#include<bits/stdc++.h>
using namespace std;
#define sf scanf
#define pf printf
const int maxn=2e5+10;
int tot=0,sum[maxn<<5],T[maxn<<5],L[maxn<<5],R[maxn<<5];
int build(int l,int r){
	int id=++tot;
	sum[id]=0;
	if(l<r){
		int mid=(l+r)>>1;
		L[id]=build(l,mid);
		R[id]=build(mid+1,r);
	}
	return id;
}
int change(int pre,int l,int r,int k){
	int id=++tot;
	if(l==r){
		sum[id]=sum[pre]+1;
		return id;
	}
	L[id]=L[pre];R[id]=R[pre];
	int mid=(l+r)>>1;
	if(k<=mid) L[id]=change(L[pre],l,mid,k);
	else R[id]=change(R[pre],mid+1,r,k);
	sum[id]=sum[L[id]]+sum[R[id]];
	return id;
}
int query(int u,int v,int l,int r,int ql,int qr){
	if(ql>qr)return 0;
	if(l>=ql&&r<=qr)return sum[v]-sum[u];
	int mid=(l+r)>>1;
	if(qr<=mid) return query(L[u],L[v],l,mid,ql,qr);
	else if(ql>mid) return query(R[u],R[v],mid+1,r,ql,qr);
	else  return query(L[u],L[v],l,mid,ql,qr)+query(R[u],R[v],mid+1,r,ql,qr);
}
struct edge{
	int u,v;
	bool operator <(const edge &t) const{
		return u<t.u;	
	}
}e[maxn];
int n,m,hash[maxn];
signed main(){
	//freopen("lx.in","r",stdin);
	sf("%d%d",&n,&m);
	for(int i=1;i<n;++i){
		sf("%d%d",&e[i].u,&e[i].v);
		if(e[i].u>e[i].v)swap(e[i].u,e[i].v);
	}
	sort(e+1,e+n);
	for(int i=1;i<n;++i)hash[i]=e[i].u;
	T[0]=build(1,n);
	for(int i=1;i<n;++i){
		T[i]=change(T[i-1],1,n,e[i].v);
	}
	int l,r;
	while(m--){
		sf("%d%d",&l,&r);
		int l1=lower_bound(hash+1,hash+n,l)-hash-1;
		pf("%d\n",query(T[0],T[l1],1,n,1,n)+query(T[l1],T[n-1],1,n,r+1,n)-(n-(r-l+1))+1);
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值