[BZOJ2959]长跑(LCT)

=== ===

这里放传送门

=== ===

题解

因为题目中说每次长跑跑道方向是固定的,也就是一条边不能来回经过。但是答案肯定不是从A沿着一条最近的路跑到B就一定最优了,可能出去绕一圈还能绕回来。那这个出去绕了一圈的过程肯定沿着两条不同的路径从A到了某个点B又从B回到了某个点A,那这可以联想到无向图的双连通分量。因为双连通分量缩点以后可以形成一棵树,那么实际上就是要用LCT维护缩点以后的图,每次加入一条边如果成环了,就把环上的所有点合并起来。一开始想要打个什么标记什么的,但里面那一坨Splay一转就转的乱七八糟。。但是实际上每个点只会被合并一次,被合并过的点再跳的时候直接跳到它的代表元素就可以了。这样的话这个题就很好写了。。但是写完以后在CodeVS上T了4个点,在Tsinsen上T了5个点。。

然后就是蛋疼的卡常过程。。尽量减少无用的找代表元素的Find过程就可以大大提高速度。然而ATP的代码虽然在BZOJ上能过但是在CodeVS上会T一个点,在Tsinsen上竟然T了4个点。。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,v[150010],lkd[150010],top;
struct Node{
    Node *ch[2],*fa;
    int sum,num,val;
    bool rev;
    Node();
    inline bool pl();
    inline bool is_root();
    void Del(Node *F);
    inline void Rev(){swap(ch[1],ch[0]);rev^=1;}
    inline void count();
    inline void push();
}*null,P[150010],*ptr[150010],*father[150010],*st[150010];
inline Node* find(Node *x){
    if (x==father[x->num]) return x;
    father[x->num]=find(father[x->num]);
    return father[x->num];
}
Node::Node(){ch[1]=ch[0]=fa=null;sum=val=num=0;rev=false;}
inline bool Node::pl(){return this==fa->ch[1];}
inline bool Node::is_root(){return this!=fa->ch[1]&&this!=fa->ch[0];}
inline void Node::count(){
    ch[0]=find(ch[0]);ch[1]=find(ch[1]);
    sum=ch[0]->sum+ch[1]->sum+val;
}
inline void Node::push(){
    if (rev==true){
        if (ch[0]!=null) ch[0]->Rev();
        if (ch[1]!=null) ch[1]->Rev();
        rev=false;
    }
}
void Node::Del(Node *F){
    if (this==null) return;
    ch[0]=find(ch[0]);ch[1]=find(ch[1]);
    ch[0]->Del(F);ch[1]->Del(F);
    father[num]=F;
    ch[1]=ch[0]=fa=null;
}
inline void Rotate(Node *k){
    Node *r=k->fa;
    if (k==null||r==null) return;
    int x=k->pl()^1;
    r->ch[x^1]=k->ch[x];
    if (r->ch[x^1]!=null)
      r->ch[x^1]->fa=r;
    if (!r->is_root())
      r->fa->ch[r->pl()]=k;
    k->fa=r->fa;
    r->fa=k;
    k->ch[x]=r;
    r->count();k->count();
}
inline void Splay(Node *r){
    top=0;Node *tmp=r;
    st[++top]=tmp;
    tmp->fa=find(tmp->fa);
    while (!tmp->is_root()){
        st[++top]=tmp->fa;
        tmp=tmp->fa;
        tmp->fa=find(tmp->fa);
    }
    for (int i=top;i>=1;i--) st[i]->push();
    for (;!r->is_root();Rotate(r))
      if (!r->fa->is_root())
        Rotate(r->pl()==r->fa->pl()?r->fa:r);
}
inline void Access(Node *k){
    Node *r=null;
    while (k!=null){
        Splay(k);
        k->ch[1]=r;
        k->count();
        r=k;k=k->fa;
    }
}
void Change_root(Node *x){Access(x);Splay(x);x->Rev();}
void Link(Node *x,Node *y){Change_root(x);x->fa=y;}
void Get_path(Node *x,Node *y){Change_root(y);Access(x);Splay(x);}
inline int check(int x){
    if (lkd[x]==x) return x;
    lkd[x]=check(lkd[x]);
    return lkd[x];
}
inline int get(){
    int x=0;
    char c=getchar();
    while (c<'0'||c>'9') c=getchar();
    while (c<='9'&&c>='0'){
        x=(x<<3)+(x<<1)+c-'0';c=getchar();
    }
    return x;
}
int main()
{
    null=new Node;*null=Node();
    n=get();m=get();
    for (int i=0;i<=n;i++){
        Node *w=&P[i];
        *w=Node();ptr[i]=w;
        father[i]=w;
        w->num=lkd[i]=i;
    }
    father[0]=ptr[0]=null;
    for (int i=1;i<=n;i++){
        v[i]=get();
        P[i].val=P[i].sum=v[i];
    }
    for (int i=1;i<=m;i++){
        int type,a,b;
        type=get();a=get();b=get();
        switch (type){
            case 1:{
                int r1,r2;
                r1=check(a);r2=check(b);
                if (r1!=r2){
                    Link(find(ptr[a]),find(ptr[b]));
                    lkd[r2]=r1;
                }else{
                    Node *tmp,*to;
                    tmp=find(ptr[a]);to=find(ptr[b]);
                    if (tmp==to) break;
                    Get_path(to,tmp);
                    Splay(to);to->val=to->sum;
                    to->Del(to);
                }
                break;
            }
            case 2:{
                Node *tmp=find(ptr[a]);
                Access(tmp);Splay(tmp);
                tmp->val=tmp->val-v[a]+b;
                v[a]=b;tmp->count();
                break;
            }
            case 3:{
                Node *tmp1,*tmp2;
                int r1,r2;
                r1=check(a);r2=check(b);
                if (r1!=r2){printf("-1\n");break;}
                tmp1=find(ptr[a]);tmp2=find(ptr[b]);
                Get_path(tmp1,tmp2);
                printf("%d\n",tmp1->sum);
                break;
            }
        }
    }
    return 0;
}

偏偏在最后出现的补充说明

这种“每个点被合并一次”的均摊分析思路还是很常见的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值