codeforces 603E(LCT+优先队列)

2 篇文章 0 订阅

我在比赛(noip模拟)时看到这题,一脸懵逼,上数学课想了很久也想不完整,总有地方实现不了。后来知道了某个dalao用LCT过的,然后和kscla吹B了半个晚上,就把这题吹出来了。

这里写图片描述
这里写图片描述

先讲LCT部分,朴素的LCT可以支持在有Link和Cut操作的森林上修改路径和维护路径信息,比如最大值。
那么就可以维护只有加边情况下的动态最小生成树。
(就是假若出现环,就把环上最大的一条边Cut掉)

Lct还可以维护连通块大小。由于LCT是维护出来的森林是有序的,即一条树链上的结点在Splay上的顺序就是深度由小到大的顺序。而path-parent也是parent,对于一条没有path-parent的路径,它的第一个结点就是这个连通块的根。

既然树是有序的,也就有了相对的子树,我们就可以像静态树一样由下往上维护子树大小。

所以朴素的想法就是按顺序,每条路径将自身子树大小传给path-parent。

可以每个结点开一个other值,表示所有path-parent指向这个点的路径所在子树大小的和。

splay可以维护other的和,记为sum,splay根上的sum+(splay大小)即为这个连通块的大小。

由此也可知,对于只在splay上的操作(不涉及path-parent的更改,如,splay,Cut中的删儿子,翻转),是不会影响other的sum的正确信息的。

现在考虑更改path-parent的操作
Access:Access就是按由下到上顺序把一个点到根的路径扔进splay里,所以就按顺序统计把当前sum算进other里。

Link(x,y):先Evert(x),再splay(x),那么x就能得到x所在连通块正确的信息。
x->path-parent=y。大概想法就是用x所在连通块的信息更新y,y所在splay的path-parent,…,一路更新到根。这样其实不影响复杂度,因为相当于Access(y)。(当然也可以Access(y)后更新一次)

如果学过LCT的套路,以上就是废话了。

对于这题,sunny图的充要条件是每个连通块都有偶数个点。(每个连通块保留一棵生成树,对于每条树边,若该边相连的两个连通块大小都为偶数,则不选该边,否则就选)。
本蒟蒻考试时这点都没看出来……

LCT题的套路:答案在最小生成树上
对于边(x,y),若存在路径(x,y),无论Cut掉路径上哪一条边,边(x,y)与被Cut掉的边都是等价的,同时选或不选。即若不Cut掉的话,当某条边作为答案时,答案需与另一条边取max,故Cut掉长度最大的边最优 。

统计答案用一个维护最大值的优先队列,存树上的边。每次操作后
不断重复:若队头不在树上,或者相连的两个连通块大小都为偶数,则把队头弹出。
这样后,队头就是答案。

我LCT,洋洋洒洒300行,还请教练把时限开大了才能A。

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>

using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))
#define imax(a, b) (((a)>(b)) ? (a) : (b))

typedef long long LL;

const int N=800800;

void read(int &hy)
{
    hy=0;
    char cc=getchar();
    while(cc>'9'||cc<'0')
    cc=getchar();
    while(cc>='0'&&cc<='9')
    {
        hy=(hy<<3)+(hy<<1)+cc-'0';
        cc=getchar();
    }
}

int js,cnt,n,m,u[N],v[N],len[N],fa[N];

int find(int x)
{
    if(fa[x]!=x)
    fa[x]=find(fa[x]);
    return fa[x];
}

struct yy{
    int num,len;  
    bool operator < (const yy &a)
    const{return len<a.len;}
}tu;

priority_queue<yy>q;

struct tree
{
    tree *c[2],*f,*pp;
    int e,me;
    bool flip,other,sum,siz,a;
    int d(){return f->c[1]==this;}
    void sc(tree *x,int d){(c[d]=x)->f=this;}
}nil[N],*ro[N],*edge[N];

inline tree *newtree(int e,bool a)
{
    nil[++cnt]=nil[0];
    nil[cnt].sum=nil[cnt].siz=nil[cnt].a=a;
    nil[cnt].me=nil[cnt].e=e;
    return nil+cnt;
}

inline void up(tree *x)
{
    x->siz=x->c[0]->siz^x->c[1]->siz^x->a;
    x->me=imax(x->e,imax(x->c[0]->me,x->c[1]->me));
    x->sum=x->a^x->other^x->c[0]->sum^x->c[1]->sum;
}

inline void down(tree *x)
{
    if(!x->flip)
    return;
    x->flip=0;
    swap(x->c[0],x->c[1]);
    x->c[0]->flip^=1;
    x->c[1]->flip^=1;
}

void work(tree *x)
{
    if(x->f!=nil)
    work(x->f);
    down(x);
}

inline void zig(tree *x)
{
    tree *y=x->f;
    int d=x->d();
    y->sc(x->c[!d],d);
    if(y->f==nil)
    x->f=nil;
    else
    y->f->sc(x,y->d());
    x->sc(y,!d);
    x->pp=y->pp;
    y->pp=nil;
    up(y);
}

inline void splay(tree *x)
{
    work(x);
    for(tree *y;x->f!=nil;)
    {
        y=x->f;
        if(y->f!=nil)
        (x->d() ^ y->d()) ? zig(x) : zig(y);
        zig(x);
    }
    up(x);
}

inline void Access(tree *x)
{
    tree *y=nil;
    while(x!=nil)
    {
        splay(x);
        if(x->c[1]!=nil)
        {
            x->c[1]->f=nil;
            x->c[1]->pp=x;
            x->other^=x->c[1]->sum;
        }
        x->c[1]=y;
        if(y!=nil)
        y->f=x;
        x->other^=y->sum;

        y->pp=nil;
        up(x);
        y=x;
        x=x->pp;
    }
}

inline void Evert(tree *x)
{
    Access(x);
    splay(x);
    x->flip^=1;
}

inline void Link(tree *x,tree *y)
{
    Evert(x);
    splay(x);
    Access(y);
    splay(y);
    x->pp=y;
    y->other^=x->sum;
    up(y);
}

inline void Cut(tree *x,tree *y)
{
    Evert(x);
    Access(y);
    splay(x);
    x->c[1]->f=nil;
    x->c[1]=nil;
    up(x);
}

void unite(int num,int X,int Y)
{
    tree *x=ro[X];
    tree *y=ro[Y];
    X=find(X);
    Y=find(Y);
    tu.num=num;
    tu.len=len[num];
    if(X^Y)
    {
        if(js!=0)
        {
            Access(x);
            splay(x);
            Access(y);
            splay(y);
            if(x->sum&&y->sum)
            js-=2;
        }
        Link(edge[num],x);
        Link(edge[num],y);
        fa[X]=Y;
        q.push(tu);
        return;
    }
    Evert(x);
    Access(y);
    splay(x);
    int le=x->me;
    if(le<=len[num])
    return;

    tree *xx=x;
    while(1)
    {
        if(xx->e==le)
        break;
        if(xx->c[0]->me==le)
        xx=xx->c[0];
        else
        xx=xx->c[1];
    }
    Cut(ro[u[xx-nil]],xx);
    Cut(ro[v[xx-nil]],xx);
    len[xx-nil]=0;

    Link(x,edge[num]);
    Link(y,edge[num]);
    fa[X]=Y;
    q.push(tu);
}

void sora()
{
    bool hy=true;
    while(hy&&!q.empty())
    {
        hy=false;
        tu=q.top();
        if(!len[tu.num])
        {
            q.pop();
            hy=true;
        }
        else
        {
            tree *x=ro[u[tu.num]];
            Cut(x,edge[tu.num]);
            Evert(x);
            if(!x->sum)
            {
                q.pop();
                hy=true;
            }
            Link(x,edge[tu.num]);
        }
    }
}

int main()
{
    nil->c[0]=nil->c[1]=nil->f=nil->pp=nil;

    cin>>n>>m;

    for(int i=1;i<=n;i++)
    fa[i]=i;

    if(n&1)
    {
        for(int i=1;i<=m;i++)
        printf("-1\n");
        return 0;
    }

    for(int i=1;i<=m;i++)
    {
        read(u[i]);
        read(v[i]);
        read(len[i]);
        edge[i]=newtree(len[i],0);
    }

    for(int i=1;i<=n;i++)
    ro[i]=newtree(0,1);

    int biu;
    js=n;
    for(biu=1;biu<=m;++biu)
    {
        unite(biu,u[biu],v[biu]);
        if(!js)
        break;
        printf("-1\n");
    }
    if(js)
    return 0;

    sora();
    tu=q.top();
    printf("%d\n",tu.len);

    for(register int i=biu+1;i<=m;++i)
    {
        unite(i,u[i],v[i]);
        sora();
        tu=q.top();
        printf("%d\n",tu.len);
    }
    return 0;
}

你知道雪为什么是白色的吗,因为她忘了自己曾经的颜色。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值