P2387 [NOI2014] 魔法森林

【题意】

一张无向图,每个边有两种边权,求1-n的路径中,经过两种边权最大值的和最小是多少

【分析】

这道题很巧妙地模仿了生成树的思想,先按照一个边权b排序,然后依次加入,这样能保证每一次的bi的最大值是不用计算的,就是当前加的bi值

只要维护ai最大值最小即可,所以我们利用LC不断加边,当x,y已经联通时,我们找到x-y路径中ai最大的一个,断开,再连上新的当前的x-y边

所以我们的LCT需要维护一个路径中的最大值出现在哪条边

【代码】

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int maxn=5e4+5;
const int maxm=2e5+5;
const int inf=0x3f3f3f3f;
struct edge
{
    int x,y,a,b;
}e[maxm];
bool cmp(edge aa,edge bb)
{
    return aa.b<bb.b;
}
int val[maxm],fa[maxm];
struct LCT
{
    int son[2],v,maxi,rev,f;
    LCT() { f = son[0] = son[1] = rev = 0; }
}tr[maxm];
#define ls(x) tr[x].son[0]
#define rs(x) tr[x].son[1]
int find(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}
int q[maxm],len;
int isroot(int x)
{
    return tr[x].f&&( ls(tr[x].f)==x || rs(tr[x].f)==x);
}
void pushup(int x)
{
    tr[x].maxi=x;
    if(ls(x) && val[tr[ls(x)].maxi]>val[tr[x].maxi]) tr[x].maxi=tr[ls(x)].maxi;
    if(rs(x) && val[tr[rs(x)].maxi]>val[tr[x].maxi]) tr[x].maxi=tr[rs(x)].maxi;
}
void pushdown(int x)
{
    if(!tr[x].rev) return;
    swap(ls(x),rs(x));
    if(ls(x)) tr[ls(x)].rev^=1;
    if(rs(x)) tr[rs(x)].rev^=1;
    tr[x].rev^=1;
}
void rotate(int x) {
    int y =tr[x].f,z=tr[y].f,b=ls(y)==x?rs(x):ls(x);
    if (z && isroot(y)) (ls(z) == y ? ls(z) : rs(z)) = x;
    tr[x].f=z; tr[y].f=x; b?tr[b].f=y:0;
    if (ls(y) == x) rs(x)= y,ls(y)= b;
    else ls(x) = y, rs(y) = b;
    pushup(y);
    pushup(x);
}
int which(int x)
{
    return x==rs(tr[x].f);
}
void splay(int x) 
{
    int i, y; q[len = 1] = x;
    for(int y=x;isroot(y);y=tr[y].f) q[++len]=tr[y].f;
    for(int i=len;i>=1;i--) pushdown(q[i]);
    while(isroot(x))
    {
        if(isroot(tr[x].f))
        {
            if (which(x) == which(tr[x].f)) rotate(tr[x].f);
            else rotate(x);
        }
        rotate(x);
    }
    pushup(x);
}
void access(int x)
{
    for(int y=0;x;y=x,x=tr[x].f)
    {
        splay(x);
        rs(x)=y;
        if(y) tr[y].f=x;
        pushup(x);
    }
}
void makeroot(int x)
{
    access(x);
    splay(x);
    tr[x].rev^=1;
}
void link(int x,int y)
{
    makeroot(x);
    tr[x].f=y;
}
void cut(int x,int y)
{
    makeroot(x);
    access(y);
    splay(y);
    tr[y].son[0]=0; tr[x].f=0;
    pushup(y);
}
int path(int x,int y)
{
    makeroot(x);
    access(y);
    splay(y);
    return tr[y].maxi;
}
int main()
{
//    freopen("forest.in","r",stdin);
//    freopen("forest.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].a,&e[i].b);
    sort(e+1,e+m+1,cmp);
    int ans=inf;
    for(int i=1;i<=m+n;i++) fa[i]=tr[i].maxi=i;
    for(int i=n+1;i<=n+m;i++) val[i]=e[i-n].a;
    for(int i=1;i<=m;i++)
    {
        int u=e[i].x,v=e[i].y,flag=1;
        if(find(u)==find(v))
        {
            int maxi=path(u,v);
            if(val[maxi]>e[i].a)
            {
                cut(e[maxi-n].x,maxi);
                cut(maxi,e[maxi-n].y);
            }
            else flag=0;
        }
        else
        {
            int fx=find(u),fy=find(v);
            if(fx!=fy) fa[fx]=fy;
        }
        if(flag)
        {    
            link(u,i+n);
            link(i+n,v);
        }
        if(find(1)==find(n))
            ans=min(ans,e[i].b+val[path(1,n)]);
    }
    printf("%d\n",ans!=inf?ans:-1);
    return 0;
}

 

吐槽:这道题目利用了并查集去加速判断,其实findroot也可,只是常数比较大,像这种只增加连通性的,最好都用并查集,有的题会卡一下下

这个并查集的fa和LCT的f一定要区分好,调了好久!!!

posted @ 2021-04-25 23:22  andyc_03  阅读(63)  评论(0编辑  收藏  举报