【BZOJ3669】【NOI2014】魔法森林 LCT

题目描述

给你一个\(n\)个点\(m\)条边的图,每条边有两个边权\(a,b\)。请你找出从\(1\)\(n\)一条路径,使得这条路径上边权\(a\)的最大值\(+\)边权\(b\)的最大值最小。

\(n\leq 50000,m\leq 100000\)

题解

我们可以考虑求出当边权\(a\leq\)某个数时边权\(b\)的最大值。

先把边按边权\(a\)从小到大排序,依次加入,用LCT维护当前边权\(b\)的最小生成树。如果这两个点已经联通,就判断这两个点路径上边的边权\(b\)的最大值,如果大于当前这条边的边权\(b\),就把这条边删掉。否则就不加入这条边。

每加完一条边我们就可以认为从\(1\)\(n\)的边权\(a\)的最大值为当前这条边的边权\(a\)(否则就会在之前更新到),然后查询\(1\)\(n\)的边权\(b\)的最大值,更新答案。

时间复杂度:\(O(m\log n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
namespace lct
{
    int a[200010][2];
    int f[200010];
    pii v[200010];
    pii s[200010];
    int r[200010];
    int root(int x)
    {
        return !f[x]||(a[f[x]][0]!=x&&a[f[x]][1]!=x);
    }
    void reverse(int x)
    {
        swap(a[x][0],a[x][1]);
        r[x]^=1;
    }
    void push(int x)
    {
        if(r[x])
        {
            if(a[x][0])
                reverse(a[x][0]);
            if(a[x][1])
                reverse(a[x][1]);
            r[x]=0;
        }
    }
    void mt(int x)
    {
        s[x]=max(v[x],max(s[a[x][0]],s[a[x][1]]));
    }
    void rotate(int x)
    {
        if(root(x))
            return;
        int p=f[x];
        int q=f[p];
        int ps=(x==a[p][1]);
        int qs=(p==a[q][1]);
        int ch=a[x][ps^1];
        if(!root(p))
            a[q][qs]=x;
        a[x][ps^1]=p;
        a[p][ps]=ch;
        if(ch)
            f[ch]=p;
        f[p]=x;
        f[x]=q;
        mt(p);
        mt(x);
    }
    void clear(int x)
    {
        if(!root(x))
            clear(f[x]);
        push(x);
    }
    void splay(int x)
    {
        clear(x);
        int p,q;
        while(!root(x))
        {
            p=f[x];
            if(!root(p))
            {
                q=f[p];
                if((p==a[q][1])==(x==a[p][1]))
                    rotate(p);
                else
                    rotate(x);
            }
            rotate(x);
        }
    }
    void access(int x)
    {
        int y=x,t=0;
        while(x)
        {
            splay(x);
            a[x][1]=t;
            mt(x);
            t=x;
            x=f[x];
        }
        splay(y);
    }
    void change(int x)
    {
        access(x);
        reverse(x);
    }
    int findroot(int x)
    {
        access(x);
        while(a[x][0])
            x=a[x][0];
        splay(x);
        return x;
    }
    pii query(int x,int y)
    {
        change(x);
        access(y);
        return s[y];
    }
    void link(int x,int y)
    {
        change(x);
        f[x]=y;
    }
    void cut(int x,int y)
    {
        change(x);
        access(y);
        f[a[y][0]]=0;
        a[y][0]=0;
        mt(y);
    }
}
struct edge
{
    int x,y;
    int a,b;
};
edge a[100010];
int cmp(edge a,edge b)
{
    return a.a<b.a;
}
int main()
{
//  freopen("bzoj3669.in","r",stdin);
//  freopen("bzoj3669.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    int i;
    for(i=1;i<=m;i++)
        scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].a,&a[i].b);
    sort(a+1,a+m+1,cmp);
    int ans=0x7fffffff;
    for(i=1;i<=m;i++)
    {
        if(a[i].x==a[i].y)
            continue;
        if(lct::findroot(a[i].x)==lct::findroot(a[i].y))
        {
            pii s=lct::query(a[i].x,a[i].y);
            if(a[i].b>=a[s.second].b)
                continue;
            lct::cut(s.second+n,a[s.second].x);
            lct::cut(s.second+n,a[s.second].y);
        }
        lct::v[i+n]=pii(a[i].b,i);
        lct::link(a[i].x,i+n);
        lct::link(a[i].y,i+n);
        if(lct::findroot(1)==lct::findroot(n))
            ans=min(ans,a[i].a+lct::query(1,n).first);
    }
    if(ans==0x7fffffff)
        ans=-1;
    printf("%d\n",ans);
    return 0;
}