训练赛---choice

选择 (choice)

题目来源

某集训试题

题面

题目描述

现在我想知道自己是否还有选择。
给定n个点m条边的无向图以及顺序发生的q个事件。
每个事件属于下面两种之一:
1.删除某一条图上仍存在的边;
2.询问是否存在两条边不相交的路径可以从点u出发到点v.

输入

第一行三个整数n,m,q
接下来m行,每行两个整数u,v,表示u和v之间有一条边
接下来q行,每行一个大写字母o和2个整数u,v,依次表示按顺序发生的q个事件:
当o为’Z’时,表示删除一条u和v之间的边
当o为’P’时,表示询问是否存在两条边不相交的路径可以从点u出发到点v
输出:
对于每组询问,如果存在,输出Yes,否则输出No (每组询问的回答用换行符隔开).

数据范围:

20%,max(n,m,p)100  
100%,max(n,m,p)100000  
实测发现,数据保证每个事件中 u!=v.

限制

时间限制: 1s
空间限制: 256M

题解

首先,这个没有强制在线,我们可以把删边转化为倒着加边。
问题在于如何维护.

官方题解给的是用并查集维护。

几个大佬分别采用了:LCA+并查集,并查集+乱搞,并查集+tarjan(边双联通分量)的做法,都有一定的道理,而且效率很高。

不过作为一个蒟蒻,我想到的做法是LCT. 虽然可以A,但是常数巨大,极限数据用时是标解的5倍左右,是时限的0.6倍左右.

怎么做呢?
原图上的点在LCT上的初始权值均设为1.
每当加入u->v边的时候,分两类情况讨论:

  • u,v尚未联通.此时利用并查集维护联通性(LCT亦可,常数较大)。将u,v在并查集中连通。设立新节点node. 之后在LCT中,link(u,node),link(node,v). 新节点的初始权值为0.这实际上相当于加入一条权值为0的边.

  • u,v已经联通.此时将LCT中u,v链上的所有点权值改为1.

每当询问u,v的时候,我们判断一下:

  • 如果u,v不联通,输出0.

  • 如果u,v联通,在LCT中查询u,v链上所有点的最小权值.若最小值为0,则输出no;若最小值为1,则输出yes.

这么做的正确性基于几个基本事实:

  • 如果u,v之间有两条边不相交的路径,那么u,v路径上任意两点之间都有两条边不相交的路径.
  • 如果 i 到 j 满足题意, j 到 k 满足题意,那么i到k也是满足题意的.
  • LCT中权值为0的边 (其实是权值为0的虚拟节点) 即是求双联通分量的时候遇到的桥边.
  • 连在已联通的树链上的边实际上就是tarjan中的返祖边.

于是就相当于是用LCT来干了一件本来应该用并查集和tarjan完成的事。

这个题读数有一些诡异,读入的是删除的边的两个端点。我开了两个堆来干这件事.先将所有边压入堆1中,将要删的边压入堆2中,然后就像在堆中删除元素那样搞就行了。

Code

#include<cstdio> 
#include<cstring> 
#include<iostream> 
#include<algorithm> 
#include<queue> 
using namespace std; 
#define MAXN 255000 
void _r(int& x) 
{ 
    char c=getchar(); 
    while(c<'0'||c>'9') 
    { 
        c=getchar(); 
    } 
    for(x=0;c>='0'&&c<='9';c=getchar()) 
    { 
        x=(x<<1)+(x<<3)+c-'0'; 
    } 
    return ; 
} 
struct node 
{ 
    int x,y,c; 
    node(int a=0,int b=0,int cc=0) 
    { 
        x=a; 
        y=b; 
        c=cc; 
    } 
    bool operator < (const node& p) const 
    { 
        return (x==p.x)?(y<p.y):(x<p.x); 
    } 
    bool operator == (const node& p) const 
    { 
        return x==p.x&&y==p.y; 
    } 
}E[MAXN]; 
int Ans[MAXN],tot; 
priority_queue<node>P1; 
priority_queue<node>P2; 
int n,m,Q; 
char op[10]; 
int ch[MAXN][2],tag[MAXN],rev[MAXN],val[MAXN],mn[MAXN],fa[MAXN]; 
int st[MAXN],top; 
bool notroot(int p) 
{ 
    return ch[fa[p]][0]==p||ch[fa[p]][1]==p; 
} 
void cal(int p) 
{ 
    int ls=ch[p][0],rs=ch[p][1]; 
    int mm=val[p]; 
    if(ls) //注意判一下,不然mm可能会意外地变成0;
    { 
        mm=min(mm,mn[ls]); 
    } 
    if(rs) 
    { 
        mm=min(mm,mn[rs]); 
    } 
    mn[p]=mm; 
    return ; 
} 
inline void zg(int x,int d) 
{ 
    int y=fa[x],z=fa[y]; 
    if(notroot(y)) 
    { 
        if(ch[z][0]==y) 
        { 
            ch[z][0]=x; 
        } 
        else 
        { 
            ch[z][1]=x; 
        } 
    } 
    fa[x]=z; 
    ch[y][d]=ch[x][!d]; 
    fa[ch[y][d]]=y; 
    ch[x][!d]=y; 
    fa[y]=x; 
    cal(y); 
    cal(x); 
    return ; 
} 
inline void push(int p) 
{ 
    int ls=ch[p][0],rs=ch[p][1]; 
    if(tag[p]) 
    { 
        tag[p]=0; 
        tag[ls]=tag[rs]=1; 
        val[ls]=val[rs]=mn[ls]=mn[rs]=1; 
    } //标记下放顺序无所谓;
    if(rev[p]) 
    { 
        rev[p]=0; 
        rev[ls]^=1; 
        rev[rs]^=1; 
        swap(ch[p][0],ch[p][1]); 
    } 
    return ; 
} 
inline void splay(int x) 
{ 
    int y,z; 
    st[++top]=x; 
    for(int i=x;notroot(i);i=fa[i]) 
    { 
        st[++top]=fa[i]; //之前把fa[i]敲成了fa[x],调了很久才发现是这个问题
    } 
    for(;top;--top) 
    { 
        push(st[top]); 
    } 
    while(notroot(x)) 
    { 
        y=fa[x]; 
        z=fa[y]; 
        if(notroot(y)) 
        { 
            if(ch[z][0]==y^ch[y][0]==x) 
            { 
                zg(x,ch[fa[x]][1]==x); 
            } 
            else 
            { 
                zg(y,ch[fa[y]][1]==y); 
            } 
        } 
        zg(x,ch[fa[x]][1]==x); 
    } 
    return ; 
} 
inline void access(int x) 
{ 
    for(int t=0;x;t=x,x=fa[x]) 
    { 
        splay(x); 
        ch[x][1]=t; 
        cal(x); 
    } 
    return ; 
} 
inline void makeroot(int x) 
{ 
    access(x); 
    splay(x); 
    rev[x]^=1; 
    return ; 
} 
void link(int x,int y) 
{ 
    makeroot(x); 
    fa[x]=y; 
    return ; 
} 
void change(int x,int y) 
{ 
    makeroot(x); 
    access(y); 
    splay(y); 
    tag[y]=1; 
    val[y]=mn[y]=1; //这里可以不动mn[y];
    return ; 
} 
int F[MAXN],N; 
int getfa(int x) 
{ 
    return F[x]==x?x:F[x]=getfa(F[x]); 
} 
inline int query(int x,int y) 
{ 
    if(getfa(x)!=getfa(y))
    {
        return 0;
    }
    makeroot(x); 
    access(y); 
    splay(y); 
    return mn[y]; 
} 
inline void Link(int x,int y) 
{ 
    int fx,fy; 
    fx=getfa(x); 
    fy=getfa(y); 
    if(fx!=fy) 
    { 
        ++N;//新建节点,初值默认为0;
        link(x,N);
        link(N,y); 
        F[fx]=fy; 
    } 
    else 
    { 
        change(x,y); 
    } 
    return ; 
} 
int main() 
{ 
    //freopen("choice.in","r",stdin);
    //freopen("choice.out","w",stdout);
    _r(n); 
    _r(m); 
    _r(Q); 
    for(int i=1,x,y;i<=m;i++) 
    { 
        _r(x); 
        _r(y); 
        if(x>y) 
        { 
            swap(x,y); 
        } 
        P1.push(node(x,y)); 
    } 
    for(int i=1,x,y;i<=Q;i++) 
    { 
        scanf("%s",op); 
        _r(x); 
        _r(y); 
        if(x>y) 
        { 
            swap(x,y); 
        } 
        if(op[0]=='Z') 
        { 
            P2.push(node(x,y)); 
            E[i]=node(x,y,0); 
        } 
        else 
        { 
            E[i]=node(x,y,1); 
        } 
    } 
    node tmp; 
    for(int i=1;i<=n;i++) 
    { 
        F[i]=i;
        val[i]=1;
    } 
    N=n;
    while(P1.size()) 
    { 
        while(P2.size()&&(P2.top()==P1.top())) 
        { 
            P2.pop(); 
            P1.pop(); 
        }
        if(P1.size())//这里一定要判!
        { 
            tmp=P1.top(); 
            P1.pop(); //先前忘写这句,直接跑死
            Link(tmp.x,tmp.y); 
        }
    } 
    for(int i=Q;i>=1;i--) 
    { 
        tmp=E[i]; 
        if(tmp.c==1) 
        { 
            Ans[i]=query(tmp.x,tmp.y); 
        } //询问
        else 
        { 
            Link(tmp.x,tmp.y); 
            Ans[i]=-1; 
        } //加边
    } 
    for(int i=1;i<=Q;i++) 
    { 
        if(Ans[i]>=0) 
        { 
            if(Ans[i]==0) 
            { 
                printf("No\n"); 
            } 
            else 
            { 
                printf("Yes\n"); 
            } 
        } 
    } 
    return 0; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值