bzoj3514 Codechef MARCH14 GERALD07加强版

考虑对于一个询问的右端点r

存在左边的一条边i,会使得存在区间i-1,r的边的时候,连通块数量比存在区间i,r的边少1

这样要查询的东西就转化成了区间l,r中,i小于l的边的数量,也就是有效边(会使连通块数量减一的边)的数量

答案就是n-ans

所以考虑维护最大删除时间生成树

每加入一条边时,如果成环,则找到边的编号最小的一条边删除掉,这条边就是r边要求的边i

 

lct维护生成树,边转化成点,具体实现细节见代码

注意:pushup的时候要判断一下儿子节点存不存在

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
using namespace std;
typedef long long ll;

const int maxn = 400100;
const int INF = 1e9+7;

int n,m,q,type,cnt,la;
int st[maxn],val[maxn<<1];

int rt[maxn],sum[maxn<<4],lc[maxn<<4],rc[maxn<<4],add[maxn<<4],tot;

struct E{
    int u,v;
}e[maxn];

struct Node{
    int ch[2];
    int fa;
    int mn,id;
    int rev;
}t[maxn<<1];

int isroot(int x){ return (t[t[x].fa].ch[0]!=x)&&(t[t[x].fa].ch[1]!=x); }

void pushup(int x){
    t[x].mn=x;
    if(t[x].ch[0]){ // 要判断儿子节点是否存在 
        if(val[t[t[x].ch[0]].mn]<val[t[x].mn]) t[x].mn=t[t[x].ch[0]].mn; // 更新编号最小的点 
    }
    if(t[x].ch[1]){
        if(val[t[t[x].ch[1]].mn]<val[t[x].mn]) t[x].mn=t[t[x].ch[1]].mn;
    }    
}

void rever(int x){
    t[x].rev^=1;
    swap(t[x].ch[0],t[x].ch[1]);
}

void pushdown(int x){
    if(t[x].rev){
        if(t[x].ch[0]) rever(t[x].ch[0]);
        if(t[x].ch[1]) rever(t[x].ch[1]);
        t[x].rev=0;
    }
}

void rotate(int x){
    int y=t[x].fa,z=t[y].fa;
    int k=(t[y].ch[1]==x);
    if(!isroot(y)) t[z].ch[t[z].ch[1]==y]=x;
    t[x].fa=z;
    t[y].ch[k]=t[x].ch[k^1];
    t[t[x].ch[k^1]].fa=y;
    t[x].ch[k^1]=y;
    t[y].fa=x;
    pushup(y); pushup(x);
}

int sta[maxn],top;
void splay(int x){
    top=0; int y=x;
    sta[++top]=y;
    
    while(t[y].fa){
        y=t[y].fa;
        sta[++top]=y;
    }
    while(top) pushdown(sta[top--]);
    
    while(!isroot(x)){
        int y=t[x].fa,z=t[y].fa;
        if(!isroot(y)){
            (t[z].ch[1]==y)^(t[y].ch[1]==x)?rotate(x):rotate(y);
        }rotate(x);
    }
    pushup(x);
}

void access(int x){
    for(int y=0;x;y=x,x=t[x].fa){ splay(x); t[x].ch[1]=y; pushup(x); }
}

void makeroot(int x){
    access(x);
    splay(x);
    rever(x);
}

int findroot(int x){
    access(x);
    splay(x);
    while(t[x].ch[0]){
        pushdown(x); // findroot不要忘记pushdown 
        x=t[x].ch[0];
    }
    return x;
}

void split(int x,int y){
    makeroot(x);
    access(y);
    splay(y);
}

void link(int x,int y){ makeroot(x); t[x].fa=y; }

void cut(int x,int y){
    makeroot(x);
    access(y);
    splay(y);
    t[y].ch[0]=t[x].fa=0;
}

int query(int x,int y){
    split(x,y); // 直接把链拎出来询问 
    return t[y].mn;
}

void build(int &i,int l,int r){
    i=++tot;
    if(l==r) return;
    int mid=(l+r)/2;
    build(lc[i],l,mid);
    build(rc[i],mid+1,r);
}

void mdf(int &i,int l,int r,int p){ // 主席树标记永久化 
    sum[++tot]=sum[i],add[tot]=add[i],lc[tot]=lc[i],rc[tot]=rc[i];
    i=tot;
    sum[i]++;
    if(l==r){
        add[i]++;
        return; 
    }
    int mid=(l+r)/2;
    if(p<=mid) mdf(lc[i],l,mid,p);
    else mdf(rc[i],mid+1,r,p);
}

int qry(int i,int ad,int l,int r,int x,int y){
    if(l==x&&r==y){ return sum[i]+ad*(r-l+1); }
    int mid=(l+r)/2;
    if(y<=mid) return qry(lc[i],ad+add[i],l,mid,x,y);
    else if(x>mid) return qry(rc[i],ad+add[i],mid+1,r,x,y);
    else{
        return qry(lc[i],ad+add[i],l,mid,x,mid)+qry(rc[i],ad+add[i],mid+1,r,mid+1,y);
    }
}

void pre(){
    cnt=n;
    int u,v;
    for(int i=1;i<=m;i++){
        u=e[i].u,v=e[i].v;
        if(u==v){
            st[i]=i;
            continue;
        }
        if(findroot(u)==findroot(v)){
            int tmp=query(u,v);
            int x=val[tmp];
            st[i]=x;
            cut(e[x].u,tmp); cut(e[x].v,tmp);
        }
        ++cnt;
        t[cnt].mn=cnt; val[cnt]=i; // val记录的是边转化成lct中的点的编号对应的原边的编号 
        link(u,cnt); link(v,cnt);
    }
    
    for(int i=1;i<=m;i++){
        rt[i]=rt[i-1];
        mdf(rt[i],0,m,st[i]);
    }
}

ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f;}
 
int main(){
    n=read(),m=read(),q=read(),type=read();
    val[0]=INF;
    for(int i=1;i<=n;i++) t[i].mn=i,val[i]=INF; // 不是边转化成的点,val值要赋为正无穷,防止被更新 
    for(int i=1;i<=m;i++) e[i].u=read(),e[i].v=read();
    
    build(rt[0],0,m);
    pre();
    la=0;

    int u,v;
    for(int i=1;i<=q;i++){
        u=read(),v=read();
        if(type==1) u^=la,v^=la;
        la=n-(qry(rt[v],0,0,m,0,u-1)-qry(rt[u-1],0,0,m,0,u-1));
        printf("%d\n",la);
    }
    return 0;
}

 

posted @ 2019-03-19 18:50  Tartarus_li  阅读(133)  评论(0编辑  收藏  举报