CF1814F Communication Towers 题解

CF1814F Communication Towers

\(n\) 个通信塔,编号从 \(1\)\(n\),它们之间有 \(m\) 条双向电线。每个通信塔都能接受一定范围的频率,第 \(i\) 个塔能接受的频率范围是 \(l_i\)\(r_i\)

我们称,如果存在某个频率 \(x\) 和一条通信塔序列 \(a=v_1, v_2, \dots, v_k=b\),其中相邻的塔之间直接通过电线连接,并且序列中的每个塔都能接受频率 \(x\),那么塔 \(b\) 从塔 \(a\) 是可达的。注意,可达性不是传递的,也就是说,如果 \(b\)\(a\) 可达,\(c\)\(b\) 可达,\(c\) 不一定从 \(a\) 可达。

你的任务是确定,从第 \(1\) 个塔出发,可以到达哪些通信塔。(\(1 \le n \le 2 \cdot 10^5\)\(0 \le m \le 4 \cdot 10^5\))(\(1 \le l_i \le r_i \le 2 \cdot 10^5\)

看数据范围知线段树分治。

将点上的范围限制转化为两点之间连边的范围限制,然后将边按范围插进一颗权值线段树中进行线段树分治。维护一个可撤销并查集,每递归到一个线段树上的节点(对应一个频率区间),就把在这个节点上挂着的边(具体地,这条边的范围完全包含这个线段树节点对应的范围)所连接的两点在并查集中合并,在线段树递归到一个叶子节点时(对应一个具体的”频率“值),就可以知道该频率下点的联通情况。回溯时,撤销并查集。

但是我们维护了并查集后还是无法直接在一个比较理想的时间下统计答案,这就是这题妙的地方。我们关心在某一频率下 \(1\) 所在的并查集中有哪些点,这个并查集构成了一颗树,我们只需要对根节点进行标记,再进行撤销操作时就可以将父亲上的信息下穿到子树上。

当然这样做还涉及了一个细节问题,假设我们将点 \(y\) 合并到点 \(x\) 上(合并后 \(y\) 的父亲为 \(x\)),假设 \(x\) 的已经在先前的频率被标记过了,但是在当前频率并未标记,则以为这当前频率下 \(x,y\) 虽然相连,但是他们并不可以通过 \(1\) 到达,故 \(y\) 不可作为答案;然而如果撤销时直接将 \(x\) 标记赋值到 \(y\) 上,会导致 \(y\) 点错误地被标记成答案。

怎么做呢,我们定义 \(tag_x\) 表示 \(x\) 可以作为答案的频率个数,在撤销时,由于我们要下放标记,故要进行 \(tag_y\leftarrow tag_y+tag_x\)。那么在之前进行合并时,执行 \(tag_y\leftarrow tag_y-tag_x\),如果 \(tag_x\) 的值在合并之后过程中没有被改变,则 \(tag_y\) 的值一来一回也不会改变,否则会加上在合并之后过程中 \(tag_x\) 改变的量,完美地实现了我们的目的。

具体实现见代码。

typedef long long ll;
typedef pair<int,int>ttfa;
typedef array<int,2>pr2;
const int N=400005,R=200000;
const ll llINF=0x3f3f3f3f3f3f3f3f;
const int INF=0x3f3f3f3f;

int n,m,lef[N],rit[N],tag[N];
pr2 lin[N];

int fat[N],siz[N],stk[N],top;
inline int fidf(int x){
	while(x!=fat[x])
		x=fat[x];
	return x;
}
inline void merge(int x,int y){
	int fx=fidf(x),fy=fidf(y);
	if(fx==fy)return;
	if(siz[fx]<siz[fy])swap(fx,fy);
	siz[fx]+=siz[fy];
	tag[fy]-=tag[fx];//important 如果在 tag[fx] 上没有更新答案,才能保证撤销时 fy 点信息是原先的
	fat[fy]=fx;
	stk[++top]=fy;
}

#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)

vector<int>lis[N<<2];
void update(int p,int l,int r,int L,int R,int id){
	if(L<=l&&r<=R){
		lis[p].push_back(id);
		return;
	}
	if(L<=mid)update(ls,l,mid,L,R,id);
	if(R>mid)update(rs,mid+1,r,L,R,id);
}
void solve(int p,int l,int r){
	int las=top;
	for(auto id:lis[p])merge(lin[id][0],lin[id][1]);
	if(l==r){
		++tag[fidf(1)];
	}else{
		solve(ls,l,mid);
		solve(rs,mid+1,r);
	}
	while(top>las){
		int y=stk[top--],x=fat[y];
		tag[y]+=tag[x];
		siz[x]-=siz[y];
		fat[y]=y;
	}
}

#undef ls
#undef rs
#undef mid

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d%d",&lef[i],&rit[i]);
		fat[i]=i,siz[i]=1;
	}
	for(int i=1;i<=m;++i){
		int u,v;scanf("%d%d",&u,&v);
		lin[i]={u,v};
		int l=max(lef[u],lef[v]);
		int r=min(rit[u],rit[v]);
		if(l<=r)update(1,1,R,l,r,i);
	}
	solve(1,1,R);
	for(int i=1;i<=n;++i)
		if(tag[i])printf("%d ",i);
	puts("");
	return 0;
}
posted @ 2025-11-27 21:25  BigSmall_En  阅读(3)  评论(0)    收藏  举报