CF687D Dividing Kingdom II 题解

Description

给定一个 n n n 个点、 m m m 条边的图,有 q q q 次询问,每次询问一个 [ l , r ] [l,r] [l,r] 的区间,求将 n n n 个点分为两个部分后,编号在 [ l , r ] [l,r] [l,r] 内的边中,两端点属于同一部分的边权最大值最小是多少。

Solution

转换一下题意,删去一些边使得剩下的图是一个二分图,使得删去的边权最大值最小。

上来看到最大值最小,立马想到二分答案,每次二分一个边权 m i d mid mid,将所有边权大于 m i d mid mid 的加入图中,用扩展域并查集判断是否是二分图。

时间复杂度 O ( q log ⁡ m ( n + m α ( n ) ) ) O(q\log m(n+m\alpha(n))) O(qlogm(n+mα(n)))

但是你发现 O ( log ⁡ m ) O(\log m) O(logm) 是没必要的,先将边按边权从大到小排序,若编号属于 [ l , r ] [l,r] [l,r] 就加入图中,如果加完这条边变奇图了那么这条边的边权就是答案。

时间复杂度 O ( q ( n + m α ( n ) ) ) O(q(n+m\alpha(n))) O(q(n+mα(n))),由于 CF的神仙机子以及 6 6 6 秒的实现可以暴力通过。

考虑如何优化,发现每一次加入边后的图实际上只有 O ( n ) O(n) O(n) 条边会对下次加边造成影响,即一些树边(可能是森林)和可能有的一条边权最大的非树边(当前答案),它们可以代表当前的图,同时一些边在多组询问中都被并入一次,而将询问上到线段树上就可以避免。

所以我们开一棵线段树,节点 [ l , r ] [l,r] [l,r] 表示将编号 [ l , r ] [l,r] [l,r] 的边并完后能代表这个图的 O ( n ) O(n) O(n) 条边,同时按边权从大到小排序,合并时先归并排序,将左右儿子的代表边集和在一起,然后求出新图的代表边集,建树复杂度为 O ( m log ⁡ m α ( n ) ) O(m\log m\alpha(n)) O(mlogmα(n))

查询时将 [ l , r ] [l,r] [l,r] 的代表边集暴力求答案,复杂度 O ( q n α ( n ) log ⁡ m ) O(qn\alpha(n)\log m) O(qnα(n)logm)

还可以继续,将询问离线离散化,树上节点 [ l , r ] [l,r] [l,r] 表示 [ b l , b r + 1 ) [b_l,b_{r+1}) [bl,br+1) 区间的代表边集,其中 b b b 是离散数组。

记得离散询问 [ l , r ] [l,r] [l,r] 时,离散 l l l r + 1 r+1 r+1,查询时取出 [ c l , c r + 1 − 1 ] [c_l,c_{r+1}-1] [cl,cr+11] 的代表集暴力即可,其中 c i c_i ci 表示 i i i 离散后的编号,若询问中没有 1 1 1 m m m,记得加进离散。

时间复杂度 O ( m log ⁡ q α ( n ) + q n α ( n ) log ⁡ q ) O(m\log q\alpha(n)+qn\alpha(n)\log q) O(mlogqα(n)+qnα(n)logq)

Code

#include<bits/stdc++.h>
using namespace std;
#define ls x<<1
#define rs x<<1|1
struct edge{
	int x,y,z;
}e[1000010];
bool cmp(edge x,edge y){
	return x.z>y.z;
}
#define ve vector<edge> 
int n,m,q,tot;
int b[6060],siz[2020],fa[2020];
ve tr[4000040];
struct que{
	int l,r;
}qu[3030];
int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}
bool uni(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy) return 0;
	if(siz[fx]>siz[fy]) swap(fx,fy);
	siz[fy]+=siz[fx];
	fa[fx]=fy;
	return 1;
}
void reset(int x){
	fa[x]=x,fa[x+n]=x+n;
	siz[x]=1,siz[x+n]=1;
}
pair<ve,int> solve(ve tmp){
	ve ans;
	for(auto x:tmp){
		reset(x.x),reset(x.y);
	}
	for(auto i:tmp){
		int x=find(i.x),y=find(i.y);
		if(x!=y){
			if(uni(i.x,i.y+n)&&uni(i.y,i.x+n)){
				ans.push_back(i);
			}
		}else{
			ans.push_back(i);
			return {ans,i.z};
			break;
		}
	}
	return {ans,-1};
}
ve merge(ve l,ve r){
	ve tmp;
	int fl1=0,fl2=0;
	while(fl1<l.size()&&fl2<r.size()){
		if(cmp(l[fl1],r[fl2])){
			tmp.push_back(l[fl1++]);
		}else{
			tmp.push_back(r[fl2++]);
		}
	}
	while(fl1<l.size()) tmp.push_back(l[fl1++]);
	while(fl2<r.size()) tmp.push_back(r[fl2++]);
	auto ans=solve(tmp);
	return ans.first;
}
void build(int x,int l,int r){
	if(l==r){
		ve tmp;
		for(int i=b[l];i<b[l+1];i++){
			tmp.push_back(e[i]);
		}
		sort(tmp.begin(),tmp.end(),cmp);
		auto ans=solve(tmp);
		tr[x]=ans.first;
		return ;
	}
	int mid=l+r>>1;
	build(ls,l,mid),build(rs,mid+1,r);
	tr[x]=merge(tr[ls],tr[rs]);
}
ve query(int x,int l,int r,int L,int R){
	if(l>=L&&r<=R){
		return tr[x];
	}
	int mid=l+r>>1;
	if(R<=mid) return query(ls,l,mid,L,R);
	if(L>=mid+1) return query(rs,mid+1,r,L,R);
	return merge(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(nullptr);
	cin>>n>>m>>q;
	for(int i=1;i<=m;i++){
		cin>>e[i].x>>e[i].y>>e[i].z;
	}
	for(int i=1;i<=q;i++){
		cin>>b[++tot]>>b[++tot];
		b[tot]++;
		qu[i]={b[tot-1],b[tot]};
	}
	b[++tot]=1;
	sort(b+1,b+1+tot);
	tot=unique(b+1,b+1+tot)-b-1;
	b[tot+1]=m+1;  //注意细节
	build(1,1,tot);
	for(int i=1;i<=q;i++){
		int l=lower_bound(b+1,b+1+tot,qu[i].l)-b;
		int r=lower_bound(b+1,b+1+tot,qu[i].r)-b-1;
		ve tmp=query(1,1,tot,l,r);
		int ans=solve(tmp).second;
		cout<<ans<<'\n';
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值