2020 Multi-University Training Contest 8 hdu6858 Discovery of Cycles(Link-Cut Tree判连通性 双指针)

题目

T(T<=10)组样例,

每次给出n(n<=3e5)个点,m(m<=3e5)条边的图,q(q<=3e5)个询问,

每次询问给出[l,r],询问只用第l,l+1,...,r条边构成的图,是否存在至少一个简单环

保证sumn<=1.5e6,summ<=1.5e6,询问强制在线

思路来源

官方题解

题解

Link-Cut Tree板子题,询问强制在线也没用,因为可以离线处理,然后O(1)回答

mn[i]:表示最小的边的编号,使[i,mn[i]]这一段的编号成环,

注意到随着i的单增,mn[i]是单调不减的,

反证法,如果mn[i+1]<mn[i],则mn[i]显然等于mn[i+1]更优,与现在的值矛盾

所以,双指针,不断往动态图里加边,

直至要加的这条边的两个端点u,v在加这条边之前已经连通,就得到了mn[i]的答案

然后删去第i条边,再继续跑双指针往右移动

 

所以需要一个能动态删边、加边、判连通的数据结构,

于是就变成LCT的裸题了,槽,怪不得120队过……

代码

#include<bits/stdc++.h>
#define lc c[x][0]
#define rc c[x][1]
using namespace std;
const int N=3e5+9;
//f:父亲 c:儿子 v:单点值 s:子树值 stk:用于从上到下释放标记的栈 r:区间翻转标记
int t,n,m,q,mn[N],f[N],c[N][2],stk[N];
struct edge{
    int u,v;
}e[N];
bool r[N];
bool nroot(int x){//判断节点是否为一个Splay的根(与普通Splay的区别1)
	return c[f[x]][0]==x||c[f[x]][1]==x;
}//原理很简单,如果连的是轻边,他的父亲的儿子里没有它
void pushup(int x){//上传信息
	//s[x]=s[lc]^s[rc]^v[x];
}
void pushr(int x){int t=lc;lc=rc;rc=t;r[x]^=1;}//翻转操作
void pushdown(int x){//判断并释放懒标记
	if(r[x]){
		if(lc)pushr(lc);
		if(rc)pushr(rc);
		r[x]=0;
	}
}
void rotate(int x){//一次旋转
	int y=f[x],z=f[y],k=c[y][1]==x,w=c[x][!k];
	if(nroot(y))c[z][c[z][1]==y]=x;c[x][!k]=y;c[y][k]=w;//额外注意if(nroot(y))语句,此处不判断会引起致命错误(与普通Splay的区别2)
	if(w)f[w]=y;f[y]=x;f[x]=z;
	pushup(y);
}
void splay(int x){//只传了一个参数,因为所有操作的目标都是该Splay的根(与普通Splay的区别3)
	int y=x,z=0;
	stk[++z]=y;//st为栈,暂存当前点到根的整条路径,pushdown时一定要从上往下放标记(与普通Splay的区别4)
	while(nroot(y))stk[++z]=y=f[y];
	while(z)pushdown(stk[z--]);
	while(nroot(x)){
		y=f[x];z=f[y];
		if(nroot(y))
			rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
		rotate(x);
	}
	pushup(x);
}
void access(int x){//访问
	for(int y=0;x;x=f[y=x]){
        splay(x),rc=y,pushup(x);
	}
}
void makeroot(int x){//换根
	access(x);splay(x);
	pushr(x);
}
int findroot(int x){//找根(在真实的树中的)
	access(x);splay(x);
	while(lc)pushdown(x),x=lc;
	splay(x);
	return x;
}
void split(int x,int y){//提取路径
	makeroot(x);
	access(y);splay(y);
}
bool connect(int x,int y){
    makeroot(x);
    return findroot(y)==x;
}
void link(int x,int y){//连边
	makeroot(x);
	if(findroot(y)!=x)f[x]=y;
}
void cut(int x,int y){//断边
	makeroot(x);
	if(findroot(y)==x&&f[y]==x&&!c[y][0]){
		f[y]=c[x][1]=0;
		pushup(x);
	}
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&n,&m,&q);
        for(int i=1;i<=m;++i){
            scanf("%d%d",&e[i].u,&e[i].v);
        }
        int now=1;
        for(int i=1;i<=m;++i){
            while(now<=m && !connect(e[now].u,e[now].v)){
                link(e[now].u,e[now].v);
                now++;
            }
            mn[i]=now;
            cut(e[i].u,e[i].v);
        }
        int las=0,k1,k2,l,r;
        while(q--){
            scanf("%d%d",&l,&r);
            k1=(l^las)%m+1;
            k2=(r^las)%m+1;
            l=min(k1,k2);
            r=max(k1,k2);
            if(mn[l]<=r){
                las=1;
                puts("Yes");
            }
            else{
                las=0;
                puts("No");
            }
        }
    }
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值