【IOI2018】狼人【Kruscal重构树】【主席树】

本文介绍了一种解决特定图论问题的算法,通过构造Kruskal重构树并结合主席树,实现对大规模数据集的有效查询。算法适用于含有大量点和边的无向图,能在O(nlogn)的时间复杂度内完成对路径属性的判断。

题意:nnn个点mmm条边的无向图,qqq次询问,每次给定s,t,L,Rs,t,L,Rs,t,L,R,判断是否存在一条sssttt的路径,使得路径上可以找到一点kkk,满足此路径s∼ks\sim ksk的部分标号都≥L\geq LLk∼tk\sim tkt标号都≤R\leq RR(均包括端点)

n,q≤2×105,m≤4×105n,q\leq2\times10^5,m\leq4\times10^5n,q2×105,m4×105

显然找到sss只走≥L\geq LL的点能到达的点集SSS,ttt只走≤R\leq RR能到达TTT,判断SSSTTT是否有交即可

分别从大到小和从小到大建出Kruscal重构树,发现SSSTTT是树上的一个子树

什么?只有点权怎么建Kruscal重构树?

因为你走一条边实际上受到了两个端点的限制,所以直接取两个点的min⁡/max⁡\min/\maxmin/max当边权就可以了

然后对SSSTTT跑dfs序,设两个dfs序数组分别为dfsa,dfsbdfsa,dfsbdfsa,dfsb

那么对于uuu点可以映射成平面上的(dfsau,dfsbu)(dfsa_u,dfsb_u)(dfsau,dfsbu)

二维数点即可

因为横纵坐标分别互不相同,所以写主席树会很清真

注意建Kruscal重构树的虚点不要加到主席树里面,否则会出奇怪的问题

复杂度O(nlog⁡n)O(n\log n)O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 600005
using namespace std;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
struct edge{int u,v;}e[MAXN];
int n,m,q;
inline bool cmp1(const edge& a,const edge& b){return max(a.u,a.v)<max(b.u,b.v);}
inline bool cmp2(const edge& a,const edge& b){return min(a.u,a.v)>min(b.u,b.v);}
inline int Min(const int& x,const int& y){return x<y? x:y;}
inline int Max(const int& x,const int& y){return x>y? x:y;}
struct KruscalRestructTree
{
	int f[MAXN],fa[MAXN][20],ch[MAXN][2],val[MAXN],cnt;
	int dfn[MAXN],ed[MAXN],pos[MAXN],tim;
	int find(int x){return f[x]==x? x:f[x]=find(f[x]);}
	inline void init()
	{
		cnt=n;
		for (int i=1;i<=n;i++) val[i]=i;
		for (int i=1;i<=n+m;i++) f[i]=i;
	}
	inline void insert(int u,int v,int m(const int&,const int&))
	{
		u=find(u),v=find(v);
		if (u==v) return;
		f[u]=f[v]=fa[u][0]=fa[v][0]=++cnt;
		ch[cnt][0]=u,ch[cnt][1]=v;
		val[cnt]=m(val[u],val[v]);
	}
	void dfs(int u)
	{
		if (!u) return;
		pos[dfn[u]=++tim]=u;
		for (int i=1;i<20;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
		dfs(ch[u][0]),dfs(ch[u][1]);
		ed[u]=tim;
	}
	inline void query(int u,int& l,int& r,int ql,int qr)
	{
		if (val[u]<ql||qr<val[u]) return (void)(l=0);
		for (int i=19;i>=0;i--)
			if (fa[u][i]&&ql<=val[fa[u][i]]&&val[fa[u][i]]<=qr)
				u=fa[u][i];
		l=dfn[u],r=ed[u];
	}
}S,T;
int ch[MAXN<<5][2],sum[MAXN<<5],cnt;
int rt[MAXN];
void insert(int& x,int y,int l,int r,int k)
{
	x=++cnt;
	ch[x][0]=ch[y][0],ch[x][1]=ch[y][1],sum[x]=sum[y]+1;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (k<=mid) insert(ch[x][0],ch[y][0],l,mid,k);
	else insert(ch[x][1],ch[y][1],mid+1,r,k);
}
int query(int x,int l,int r,int ql,int qr)
{
	if (ql<=l&&r<=qr) return sum[x];
	if (qr<l||r<ql) return 0;
	int mid=(l+r)>>1;
	return query(ch[x][0],l,mid,ql,qr)+query(ch[x][1],mid+1,r,ql,qr);
}
int main()
{
	n=read(),m=read(),q=read();
	S.init(),T.init();
	for (int i=1;i<=m;i++) e[i].u=read()+1,e[i].v=read()+1;
	sort(e+1,e+m+1,cmp2);
	for (int i=1;i<=m;i++) S.insert(e[i].u,e[i].v,Min);
	sort(e+1,e+m+1,cmp1);
	for (int i=1;i<=m;i++) T.insert(e[i].u,e[i].v,Max);
	int tot=S.cnt;
	S.dfs(tot),T.dfs(tot);
	for (int i=1;i<=tot;i++) 
		if (S.pos[i]<=n) insert(rt[i],rt[i-1],1,tot,T.dfn[S.pos[i]]);
		else rt[i]=rt[i-1];
	while (q--)
	{
		int s,t,l,r;
		s=read()+1,t=read()+1,l=read()+1,r=read()+1;
		int lx,rx,ly,ry;
		S.query(s,lx,rx,l,tot),T.query(t,ly,ry,1,r);
		if (!lx||!ly){puts("0");continue;}
		int ans=query(rt[rx],1,tot,ly,ry)-query(rt[lx-1],1,tot,ly,ry);
		printf("%d\n",!!ans);
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值