【题解】[IOI2018] werewolf 狼人
题目分析
这个题本身很简单,可能就是因为是 IOI 题所以看上去就难了些吧。
其实题目就是让我们先走一段全部大于等于 \(l\) 的点然后再走一段全部小于等于 \(r\) 的点,那么能成功走过显然就是从 \(s\) 开始全部走大于等于 \(l\) 能到的点与从 \(e\) 开始走全部大于等于 \(r\) 能到的点有交。
那么现在就是怎么去找那些点的问题了,我们可以想到在 \(Kruskal\) 重构树中从一个点走到另一个点的子树内所满足的性质有点类似上面这个,只不过需要考虑一下边权怎么弄。其实很好搞,对于从 \(s\) 开始的将边权设为连接的点的编号的 \(\min\) 从 \(e\) 开始的则设为 \(\max\) 分别建 \(Kruskal\) 重构树就可以将上述条件转化为两棵子树判断是否有交了。
看上去还是不好搞,我们也知道对于同一棵子树内的节点其 \(dfs\) 序,所以我们其实就是可以转化为给定两个排列 \(a,b\),分别划定一段区间,判断区间是否有交。
那么我们就可以使用类似映射,即求出 \(b\) 中的元素在 \(a\) 位置,然后就是判断 \(b\) 的这一段对应的权值中 \(a\) 的那一段区间和是否为 \(0\)。
直接主席树去维护就好了。
感觉这个题的难点最在于一开始的条件转化,之后就是套路分析了。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
struct edge{
int nxt,to,val,from;
edge(){}
edge(int _nxt,int _to){
nxt = _nxt,to = _to;
}
};
int n,m,q,tot,sum[60*N],lson[60*N],rson[60*N],pos[N],rt[N];
bool cmp1(edge a,edge b){return a.val > b.val;}
bool cmp2(edge a,edge b){return a.val < b.val;}
struct Kruskal{
edge e[2*N],a[2*N];
int tmp,cnt,head[N],in[N],out[N],dfn[N],val[N],fa[N][22],res,f[N],id[N];
bool mark = false;
void add_edge(int from,int to){
e[++cnt] = edge(head[from],to);
head[from] = cnt;
}
int find(int x){
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
void dfs(int now,int fath){
fa[now][0] = fath;in[now] = tmp + 1;
if(now <= n) dfn[now] = ++tmp,id[tmp] = now;
for(int i = head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(to == fath) continue;
dfs(to,now);
}
out[now] = tmp;
}
void pre_lca(){
for(int i=1; i<=20; i++){
for(int j=1; j<=2*n-1; j++){
fa[j][i] = fa[fa[j][i-1]][i-1];
}
}
}
void init(){
for(int i=1; i<=2*n-1; i++) f[i] = i;
if(mark) sort(a+1,a+m+1,cmp1);
else sort(a+1,a+m+1,cmp2);
int res = n;
for(int i=1; i<=m; i++){
int x = find(a[i].from),y = find(a[i].to);
if(x != y){
f[x] = f[y] = ++res;
val[res] = mark ? min(a[i].from,a[i].to) : max(a[i].from,a[i].to);
add_edge(x,res);add_edge(res,x);
add_edge(y,res);add_edge(res,y);
}
}
dfs(res,0);
pre_lca();
}
int get1(int now,int x){
for(int i = 20; i>=0; i--){
if(fa[now][i] && val[fa[now][i]] >= x){
now = fa[now][i];
}
}
return now;
}
int get2(int now,int x){
for(int i=20; i>=0; i--){
if(fa[now][i] && val[fa[now][i]] <= x){
now = fa[now][i];
}
}
return now;
}
}A,B;
int insert(int lst,int now_l,int now_r,int pos){
int now = ++tot;
if(now_l == now_r){
sum[now] = sum[lst] + 1;
return now;
}
int mid = (now_l + now_r)>>1;
if(pos <= mid) lson[now] = insert(lson[lst],now_l,mid,pos),rson[now] = rson[lst];
else rson[now] = insert(rson[lst],mid+1,now_r,pos),lson[now] = lson[lst];
sum[now] = sum[lson[now]] + sum[rson[now]];
return now;
}
int query(int l,int r,int now_l,int now_r,int ql,int qr){
if(ql <= now_l && qr >= now_r) return sum[r] - sum[l];
int mid = (now_l + now_r)>>1,ans = 0;
if(ql <= mid) ans += query(lson[l],lson[r],now_l,mid,ql,qr);
if(qr > mid) ans += query(rson[l],rson[r],mid+1,now_r,ql,qr);
return ans;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
A.mark = true;
scanf("%d%d%d",&n,&m,&q);
for(int i=1; i<=m; i++){
int from,to;scanf("%d%d",&from,&to);
from++;to++;
A.a[i].from = from;A.a[i].to = to;A.a[i].val = min(from,to);
B.a[i].from = from;B.a[i].to = to;B.a[i].val = max(from,to);
}
A.init();B.init();
for(int i=1; i<=n; i++) pos[i] = A.dfn[i];
for(int i=1; i<=n; i++) rt[i] = insert(rt[i-1],1,n,pos[B.id[i]]);
// printf("\n");
for(int i=1; i<=q; i++){
int s,t,l,r;scanf("%d%d%d%d",&s,&t,&l,&r);
s++;t++;l++;r++;
s = A.get1(s,l);t = B.get2(t,r);
// printf("%d %d\n",s,t);
printf("%d\n",query(rt[B.in[t]-1],rt[B.out[t]],1,n,A.in[s],A.out[s]) >= 1);
// printf("%d %d %d %d\n",B.in[t]-1,B.out[t],A.in[s],A.out[s]);
}
return 0;
}