【NOI2014】魔法森林

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

Description

给出一张n个点,m条边的图,每条边有两个边权(a,b),求从1到n的路径中所有的权a最大值+权b最大值的最小值是多少。
n<=50000,m<=100000

Solution

长得那么像双关键字的最小生成树。
不过是最大值的和而已。
那么我们可以把所有的边按a值从小到大排序。
然后依次加边,如果形成了环那么就删去环上b值最大的边。
原因嘛参照kurskal的证明~
我才不会说我不会证
也就是说我们要动态加边,删边,求最大值。
LCT嘛。。。
话说各位说用LCT神犇好像都是用spfa虐过去的啊

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 150005
using namespace std;
struct note{int x,y,a,b;}a[N];
bool cmp(note x,note y) {return x.a<y.a;}
int t[N][2],f[N],p[N],mx[N],key[N],d[N],rev[N];
int n,m,ans,fa[N]; 
int get(int x) {return fa[x]?fa[x]=get(fa[x]):x;}
int son(int x) {
    return t[f[x]][1]==x;
}
void updata(int x) {
    int l=t[x][0],r=t[x][1];
    if (a[mx[l]].b>a[mx[r]].b) mx[x]=mx[l];
    else mx[x]=mx[r];
    if (a[key[x]].b>a[mx[x]].b) mx[x]=key[x];
}
void down(int x) {
    if (rev[x]) {
        rev[x]=0;
        if (t[x][0]) rev[t[x][0]]^=1;
        if (t[x][1]) rev[t[x][1]]^=1;
        swap(t[x][0],t[x][1]);
    }
}
int remove(int x,int y) {
    do {
        d[++d[0]]=x;x=f[x];
    } while (x!=y);
    while (d[0]) down(d[d[0]--]);
}
void rotate(int x) {
    int y=f[x],z=son(x);f[x]=f[y];
    if (f[x]) t[f[x]][son(y)]=x;
    else p[x]=p[y],p[y]=0;
    if (t[x][1-z]) f[t[x][1-z]]=y;
    f[y]=x;t[y][z]=t[x][1-z];t[x][1-z]=y;
    updata(y);updata(x);
}
void splay(int x,int y) {
    remove(x,y);
    while (f[x]!=y) {
        if (f[f[x]]!=y)
            if (son(x)==son(f[x])) rotate(f[x]);
            else rotate(x);
        rotate(x);
    }
}
void access(int x) {
    int y=0;
    while (x) {
        splay(x,0);
        f[t[x][1]]=0;p[t[x][1]]=x;
        t[x][1]=y;p[y]=0;f[y]=x;
        updata(x);y=x;x=p[x];
    }
}
void makeroot(int x) {
    access(x);splay(x,0);rev[x]^=1;
}
void link(int x,int y) {
    makeroot(x);p[x]=y;
}
void cut(int x,int y) {
    makeroot(x);access(y);splay(y,0);
    t[y][0]=0;p[x]=f[x]=0;
    updata(y);
}
int find(int x,int y) {
    makeroot(x);access(y);splay(y,0);
    return mx[y];
}
int main() {
    scanf("%d%d",&n,&m);
    fo(i,1,m) scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].a,&a[i].b),key[i+n]=i;
    sort(a+1,a+m+1,cmp);ans=0x7fffffff;
    fo(i,1,m) {
        if (a[i].x==a[i].y) continue;
        int ax=get(a[i].x),by=get(a[i].y);
        if (ax==by) {
            int l=find(a[i].x,a[i].y);
            if (a[l].b>a[i].b) {
                cut(a[l].x,l+n);cut(a[l].y,l+n);
                link(a[i].x,i+n);link(a[i].y,i+n);
            }
        } else {
            fa[by]=ax;
            link(a[i].x,i+n);link(a[i].y,i+n);
        }
        ax=get(1);by=get(n);
        if (ax==by) ans=min(ans,a[i].a+a[find(1,n)].b);
    }
    if (ans==0x7fffffff) printf("-1");else printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值