【最小生成树】【LCT】【bzoj2594】水管局长数据加强版

3 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:
给定无向图,动态进行删除边,查询两点路径最大边权值。

题解:
1. 首先考虑离线操作倒序进行插入边操作。
2. 之后考虑新加进来的边一定会在原来的最小生成树上产生环,找到环上的最大权删去就可以更新最小生成树。
3. 就相当于加边时判断新加的边两端点在原最小生成树上路径最大值和此边边权关系,新加的小就断开原来的最大边,连接新边。
4. 3的过程可以用LCT维护。
5. LCT只能维护点权,边权需要将边转化成点,把边标号建立成点,连接某两点时直接先连接某一点到边对应的点,再连接边对应点到另一点。
6. 这里有小技巧就是把边对应点和原图上的点标号成一个序列,边对应标号为原边对应标号+n。
7. 不过这里我没用6,我直接进行的标号,这样不是很容易实现,需要多次转化标号。
8. 注意:千万不能把LCT里面写错,否则还得调一天。
9. 这类LCT里面只能对,不能错!!!!

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
struct node{
    int rev,val,nu;//nu:对应原边标号
    node *ch[2],*fa,*mx;
    int dir(){return this==fa->ch[1];}
    void cnct(node *p,int d){ch[d]=p;p->fa=this;}
    void down();
    void up();
}tnull,*null=&tnull,*tree[1000001],*ed[1000001];
void node::down(){
    if(this==null) return;
    if(rev){
        if(ch[0]!=null) ch[0]->rev^=1;
        if(ch[1]!=null) ch[1]->rev^=1;
        this->rev=0;
        swap(ch[0],ch[1]);
    }
}
node *getnode(int k,int l){
    node *p=new node;
    p->nu=l;
    p->rev=0;
    p->val=k;
    p->mx=p;
    p->ch[0]=p->ch[1]=p->fa=null;
    return p;
}
void node::up(){
    mx=this;
    if(ch[0]!=null&&ch[0]->mx->val>mx->val) mx=ch[0]->mx;
    if(ch[1]!=null&&ch[1]->mx->val>mx->val) mx=ch[1]->mx;
}
bool isroot(node *p){return p==null||p!=p->fa->ch[0]&&p!=p->fa->ch[1];}
void rotate(node *p){
    int d=p->dir();node *x=p->fa;
    if(isroot(p)) return;
    if(x==x->fa->ch[0])
        x->fa->ch[0]=p;
    else if(x==x->fa->ch[1])
        x->fa->ch[1]=p;
    p->fa=x->fa;
    x->cnct(p->ch[!d],d);
    p->cnct(x,!d);
    x->up();
}
void splay(node *p){
    static node* sta[1000001];int top=0;
    sta[++top]=p;
    for(node *k=p;!isroot(k);k=k->fa)
        sta[++top]=k->fa;
    while(top)
        sta[top--]->down();
    while(!isroot(p)){
        if(isroot(p->fa)) {rotate(p);break;}
        else
            if(p->dir()==p->fa->dir()) rotate(p->fa),rotate(p);
            else rotate(p),rotate(p);
    }
    p->up();
}
void access(node *p){
    node *q=null;
    while(p!=null){
        splay(p);
        p->cnct(q,1);
        p->up();
        q=p;
        p=p->fa;
    }
}
void mtr(node *p){
    access(p);
    splay(p);
    p->rev^=1;//!!!!!!!!^=!!!!!!!!
}
void link(node *p,node *q){
    mtr(p);
    p->fa=q;
}
void cut(node *p,node *q){
    mtr(p);
    access(q);
    splay(q);
    q->ch[0]=q->ch[0]->fa=null;
    q->up();
}
node* query(node *p,node *q){
    mtr(p);
    access(q);
    splay(q);
    return q->mx;
}
node* getroot(node *p){
    while(p->fa!=null) p=p->fa;
    return p;
}
struct edge{
    int from,to,val,d,num;
    edge(int a,int b,int c):from(a),to(b),val(c){d=0;}
    edge(){d=0;}
    bool operator < (edge b) const{//查找插入边在原边标号用的
    if(from==b.from)
        return to<b.to;
    return from<b.from;
    }
};
bool cmp1(edge a,edge b){return a.num<b.num;}//按原序排序
bool cmp2(edge a,edge b){return a.val<b.val;}//mst排序
int n,m,Q;
edge e[1000001];
edge pre[1000001];
struct action{
    int kd,from,to,ans,num;//num是插入边在原边标号
    action(int a,int b,int c):kd(a),from(b),to(c){}
    action(){}
};
action q[1000001];
int read(){//这题时间25秒,这个常数可以忽略
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int main(){
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=n;i++)
        tree[i]=getnode(0,0);
    for(int i=1;i<=m;i++){
        int a=read(),b=read(),c=read();//scanf("%d%d%d",&a,&b,&c);
        ed[i]=getnode(c,i);
        if(a>b) swap(a,b);
        e[i]=edge(a,b,c);
        e[i].num=i;
    }
    sort(e+1,e+m+1);
    for(int i=1;i<=Q;i++){
        int a=read(),b=read(),c=read();
        // scanf("%d%d%d",&a,&b,&c);
        if(b>c) swap(b,c);
        q[i]=action(a,b,c);
        if(a==2){
            int k=lower_bound(e+1,e+m+1,edge(b,c,0))-e;
            q[i].num=e[k].num;
            e[k].d=1;
        }
    }
    sort(e+1,e+m+1,cmp2);
    int cnt=0;
    for(int i=1;i<=m;i++)
        if(!e[i].d){
            edge t=e[i];
            if(getroot(tree[t.from])!=getroot(tree[t.to])){
                link(tree[t.from],ed[t.num]);
                link(tree[t.to],ed[t.num]);
                cnt++;
            }
            if(cnt==n-1) break;
        }
    sort(e+1,e+m+1,cmp1);
    for(int i=Q;i>=1;i--){
        if(q[i].kd==1)
            q[i].ans=query(tree[q[i].from],tree[q[i].to])->val;
        else{
            node *p=query(tree[q[i].from],tree[q[i].to]);
            if(p->val>e[q[i].num].val){
                cut(tree[e[p->nu].from],p);
                cut(tree[e[p->nu].to],p);
                link(tree[q[i].from],ed[q[i].num]);
                link(tree[q[i].to],ed[q[i].num]);
            }
        }
    }
    for(int i=1;i<=Q;i++)
        if(q[i].kd==1)
        printf("%d\n",q[i].ans);
    return 0;
}

LCT不能错!!!!不能错!!!!不能错!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值