[NOI2014][JZOJ3754][BZOJ3669]魔法森林

10 篇文章 0 订阅
10 篇文章 0 订阅

题目大意

给定一个 n 个点m条边的无向图。每条边有两个权值 (ai,bi)
你需要找到一条从 1 n的路径,使得路径上 ai 最大值与 bi 最大值的和尽量小。

2n5×104,0m105,1ai,bi5×104


题目分析

这题看上去就很mst套路,实际上也是mst套路。
考虑从小到大枚举路径上 ai 的最大值,然后加入满足条件的边。
如果形成了环就删掉环上 bi 最大的边。如果 1 n是联通的就更新答案。
使用LCT维护森林,使用并查集维护连通性。
时间复杂度 O(nlogn+nα(n))


代码实现

人生第一发lct居然很快就调过了,exciting!

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <stack>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int INF=1000000000;
const int N=50005;
const int M=100005;
const int V=N+M;

struct edge
{
    int x,y,a,b;

    bool operator<(edge const ed)const{return a<ed.a;}

    void load(){x=read(),y=read(),a=read(),b=read();}
}edg[M];

int fa[N],rank[N];
int n,m,ans;

struct link_cut_tree
{
    int par[V],fa[V],size[V],id[V],mx[V];
    int son[V][2];
    stack<int> st;
    bool mark[V];

    bool side(int x){return son[fa[x]][1]==x;}

    void R(int x){swap(son[x][0],son[x][1]),mark[x]^=1;}

    void clear(int x)
    {
        if (mark[x])
        {
            if (son[x][0]) R(son[x][0]);
            if (son[x][1]) R(son[x][1]);
            mark[x]=0;
        }
    }

    void update(int x)
    {
        size[x]=size[son[x][0]]+size[son[x][1]]+1;
        mx[x]=edg[mx[son[x][0]]].b>edg[mx[son[x][1]]].b?mx[son[x][0]]:mx[son[x][1]];
        mx[x]=edg[id[x]].b>edg[mx[x]].b?id[x]:mx[x];
    }

    void pushdown(int x,int y)
    {
        for (;x!=y;st.push(x),x=fa[x]);
        for (;!st.empty();clear(st.top()),st.pop());
    }

    void rotate(int x)
    {
        int y=fa[x];bool s=side(x);
        if (fa[y]) son[fa[y]][side(y)]=x;
        if (son[x][s^1]) fa[son[x][s^1]]=y;
        son[y][s]=son[x][s^1],son[x][s^1]=y;
        fa[x]=fa[y],fa[y]=x;
        if (par[y]) par[x]=par[y],par[y]=0;
        update(y),update(x);
    }

    void splay(int x,int y)
    {
        for (pushdown(x,y);fa[x]!=y;rotate(x))
            if (fa[fa[x]]!=y)
                if (side(x)==side(fa[x])) rotate(fa[x]);
                else rotate(x);
    }

    int access(int x)
    {
        int nxt=0;
        for (;x;update(nxt=x),x=par[x])
        {
            splay(x,0);
            if (son[x][1]) fa[son[x][1]]=0,par[son[x][1]]=x;
            son[x][1]=nxt;
            if (nxt) fa[nxt]=x,par[nxt]=0;
        }
        return nxt;
    }

    void makeroot(int x){R(access(x));}

    void link(int x,int y)
    {
        makeroot(x),access(x),splay(x,0);
        par[x]=y,access(x);
    }

    void cut(int x,int y)
    {
        makeroot(x),access(y),splay(y,0);
        fa[x]=son[y][0]=0,par[y]=0,update(y);
    }

    int query(int x,int y)
    {
        makeroot(x),access(y),splay(y,0);
        return mx[y];
    }
}lct;

int getfather(int son){return fa[son]==son?son:fa[son]=getfather(fa[son]);}

void merge(int x,int y)
{
    if (rank[x]>rank[y]) swap(x,y);
    fa[y]=x,rank[y]+=rank[x]==rank[y];
}

void calc()
{
    for (int i=1;i<=n;++i) fa[i]=i;
    sort(edg+1,edg+1+m),ans=INF;
    for (int i=1;i<=n+m;++i) lct.size[i]=1;
    for (int i=1;i<=m;++i)
    {
        int x=edg[i].x,y=edg[i].y,fx=getfather(x),fy=getfather(y);
        if (fx!=fy) merge(fx,fy),lct.mx[i+n]=lct.id[i+n]=i,lct.link(x,i+n),lct.link(i+n,y);
        else
        {
            int id=lct.query(x,y);
            if (edg[id].b>edg[i].b)
            {
                int u=edg[id].x,v=edg[id].y;
                lct.cut(u,id+n),lct.cut(id+n,v);
                lct.mx[i+n]=lct.id[i+n]=i,lct.link(x,i+n),lct.link(i+n,y);
            }
        }
        if (getfather(1)==getfather(n)) ans=min(ans,edg[i].a+edg[lct.query(1,n)].b);
    }
}

int main()
{
    freopen("forest.in","r",stdin),freopen("forest.out","w",stdout);
    n=read(),m=read();
    for (int i=1;i<=m;++i) edg[i].load();
    calc();
    if (ans==INF) printf("-1\n");
    else printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值