[ioi2018 d1t3] werewolf - kruskal重构树 - 二维数点 - 主席树

题目大意:给定n个点m条边的无向简单连通图,k次询问,每次给出两个点u,v和两个限制l,r,询问是否有一条从u到v的简单路径满足:路径上有一个分界点,前一半经过的点(包括u和分界点)的编号都>=l,后一半经过的点(包括v和分界点)的编号都<=r。

全场大约十几人ac,我们全机房一块研究了一天终于搞出了一个像样的做法。(顺便%一下某ckw同学,看到题面16分钟直接秒掉!)

这么工业的题一看就是中国人出的。这道题是一道对中国选手比较友好的题目,全场17人ac,为yml没有现场a掉感到可惜。

我们分别考虑两个限制。以l来说,当l=n时我们哪也走不了,随着l减小,能到达的区域在渐渐合并,最后合并成一个连通块。

是不是有点像kruskal最小生成树的过程?

那么我们就需要考虑维护两棵生成树,然后动态查询两部分分别拎出一个连通块来,它们是否有交集。

去过今年noi的很容易发现,这不就是noi d1t1用到的黑科技——kruskal生成树嘛!

(此处安利noi d1t1题解:https://blog.csdn.net/liuzhangfeiabc/article/details/81114640

具体来说,每次我们选出下一个节点,把它能合并的那些连通块的根节点的父亲全部指向它,然后把它当做新的根。

(还有一点省事之处就是连新建节点都不需要,因为我们是天然地按照点权顺序合并。)

于是问题就变成了:从两棵树上各选出一个子树(用倍增查询),问两个子树有没有公共点。

这就变成了经典的二维数点问题,可以用各种数据结构维护。这里选用主席树的原因是,这样我们甚至能支持强制在线!

//以下代码根据ioi提交格式编写

#include<bits/stdc++.h>
using namespace std;
#include"werewolf.h"
int n,m,k;
struct edge{
	int to,nxt;
}e[1000010];
int cnt,fir[200010];
void ins(int u,int v){
	e[++cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt;
	e[++cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt;
}
int f[200010];
int getf(int q){
	return f[q] == q ? q : f[q] = getf(f[q]);
}
int fsts1[200010],nxt1[200010],fsts2[200010],nxt2[200010];
int st1[20][200010],st2[20][200010];
int nw1,nw2,wz1[200010],wz2[200010],ed1[200010],ed2[200010],dfsx2[200010];
void dfs1(int q){
	wz1[q] = ++nw1;
	for(int i = fsts1[q];i;i = nxt1[i]) dfs1(i);
	ed1[q] = nw1;
}
void dfs2(int q){
	wz2[q] = ++nw2;
	dfsx2[nw2] = q;
	for(int i = fsts2[q];i;i = nxt2[i]) dfs2(i);
	ed2[q] = nw2;
}
inline void buildst(){
	register int i,j;
	for(i = 1;i < 20;++i) for(j = 1;j <= n;++j) st1[i][j] = st1[i - 1][st1[i - 1][j]],st2[i][j] = st2[i - 1][st2[i - 1][j]];
} 
int rt[200010],ct,t[5000010],ls[5000010],rs[5000010];
#define ln ls[q],l,mid
#define rn rs[q],mid + 1,r
#define md int mid = l + r >> 1
void ins(int p,int &q,int l,int r,int x){
	q = ++ct;
	if(l == r){
		t[q] = t[p] + 1;
		return;
	}
	md;
	if(mid >= x){
		ins(ls[p],ln,x);
		rs[q] = rs[p];
	}
	else{
		ls[q] = ls[p];
		ins(rs[p],rn,x);
	}
	t[q] = t[ls[q]] + t[rs[q]];
}
int qy(int q,int l,int r,int al,int ar){
	if(!q) return 0;
	if(l >= al && r <= ar) return t[q];
	md;
	int as = 0;
	if(mid >= al) as = qy(ln,al,ar);
	if(mid < ar) as += qy(rn,al,ar);
	return as;
}
inline int find1(int u,int x){
	for(int i = 19;i >= 0;--i) if(st1[i][u] >= x) u = st1[i][u];
	return u;
}
inline int find2(int u,int x){
	for(int i = 19;i >= 0;--i) if(st2[i][u] && st2[i][u] <= x) u = st2[i][u];
	return u;
} 
vector<int> as;
vector<int> check_validity(int _n,vector<int> _x,vector<int> _y,vector<int> _s,vector<int> _e,vector<int> _l,vector<int> _r){
	n = _n;m = _x.size();k = _s.size();as.resize(k);
	int i,j,u,v,l,r;
	for(i = 0;i < m;++i){
		u = _x[i] + 1;v = _y[i] + 1;ins(u,v);
	}
	for(i = 1;i <= n;++i) f[i] = i;
	for(i = n;i;--i) for(j = fir[i];j;j = e[j].nxt) if(e[j].to > i){
		l = getf(e[j].to);if(l == i) continue;
		f[l] = st1[0][l] = i;nxt1[l] = fsts1[i];fsts1[i] = l;
	}
	for(i = 1;i <= n;++i) f[i] = i;
	for(i = 1;i <= n;++i) for(j = fir[i];j;j = e[j].nxt) if(e[j].to < i){
		l = getf(e[j].to);if(l == i) continue;
		f[l] = st2[0][l] = i;nxt2[l] = fsts2[i];fsts2[i] = l;
	}
	dfs1(1);dfs2(n);
	buildst();
	for(i = 1;i <= n;++i) ins(rt[i - 1],rt[i],1,n,wz1[dfsx2[i]]);
	for(i = 0;i < k;++i){
		u = _s[i] + 1;v = _e[i] + 1;l = _l[i] + 1;r = _r[i] + 1;
		if(u < l || v > r || l > r){
			as[i] = 0;
			continue;
		}
		u = find1(u,l);v = find2(v,r);
		as[i] = qy(rt[ed2[v]],1,n,wz1[u],ed1[u]) - qy(rt[wz2[v] - 1],1,n,wz1[u],ed1[u]) > 0;
	}
	return as;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值