UOJ #3. 【NOI2014】魔法森林

题面

题意

给出一张无向图,每条边有a,b两个权值,问从1到n的所有路中,经过的边的a最大值与b的最大值之和的最小值是多少

做法

总体思路是枚举a,b中的一个值,然后对边按其中一维进行排序,然后一边加边,一边求出此时的最短路来更新答案.

法一:SPFA(非正解)

每次加边后用SPFA算一次最短路,这样虽然可以过原题的所有数据,但是复杂度不正确,无法过UOJ上的hack数据,因而不推荐.

代码

#include
#include
#include
#include
#include
#define INF 0x3f3f3f3f
#define N 50010
#define M 100100
using namespace std;

int n,m,first[N],d[N],bb,ans=INF;
bool in[N];
struct B
{
    int from,to,a,b;
    bool operator < (const B &u) const
    {
        return bstruct Bn
{
    int to,next,quan;
}bn[M<<1];
queue<int>que;

inline void add(int u,int v,int w)
{
    bb++;
    bn[bb].to=v;
    bn[bb].next=first[u];
    bn[bb].quan=w;
    first[u]=bb;
}

inline void ad(int u,int v,int w)
{
    add(u,v,w);
    add(v,u,w);
}

int main()
{
    memset(first,-1,sizeof(first));
    memset(d,0x3f,sizeof(d));
    int i,j,k,t,p,q;
    cin>>n>>m;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&b[i].from,&b[i].to,&b[i].a,&b[i].b);
    }
    sort(b+1,b+m+1);
    d[1]=0;
    for(k=1;k<=m;)
    {
        t=b[k].b;
        que.push(1);
        for(;k<=m&&b[k].b==t;k++)
        {
            ad(b[k].from,b[k].to,b[k].a);
            if(d[b[k].from] < d[b[k].to])
            {
                if(max(d[b[k].from],b[k].a)else if(d[b[k].from] != d[b[k].to])
            {
                if(max(d[b[k].to],b[k].a)for(;!que.empty();)
        {
            q=que.front();
            que.pop();
            in[q]=0;
            for(p=first[q];p!=-1;p=bn[p].next)
            {
                if(d[bn[p].to]<=max(d[q],bn[p].quan)) continue;
                d[bn[p].to]=max(d[q],bn[p].quan);
                if(!in[bn[p].to])
                {
                    in[bn[p].to]=1;
                    que.push(bn[p].to);
                }
            }
        }
        ans=min(t+d[n],ans);
    }
    ans==INF?puts("-1"):printf("%d",ans);
}

法二:LCT

可以发现加边之后其实只要维护此时的最小生成树就可以更新后面的答案了,而维护这个最小生成树需要进行link,cut等操作,因而不难想到LCT.
对于每一个点和每一条边都建一个动态树上的点,记录这个点的子树中的最大边权的位置,然后每次加入边(u->v)时若会出现环,则找到u->v这条链上的最大边,然后比较权值,除非u->v的权值较大,否则cut那条最大的(因为每条边也是一个点,因而要cut两次),并且link(u,v)(同样要link两条边).每次更新答案即可.

代码

#include
#include
#include
#define INF 0x3f3f3f3f
#define N 150100
using namespace std;

int n,m,fa[N],bcj[N],son[N][2],mx[N],val[N],ans=INF;
bool fz[N];
struct Bn
{
    int to,from,a,b;
    bool operator < (const Bn &u) const
    {
        return binline int fb(int u){return u==bcj[u]?u:bcj[u]=fb(bcj[u]);}
inline bool ar(int u){return son[fa[u]][0]!=u && son[fa[u]][1]!=u;}
inline bool as(int u){return u==son[fa[u]][1];}
inline void up(int u)
{
    if(!u) return;
    mx[u]=u;
    if(val[mx[son[u][0]]]>val[mx[u]]) mx[u]=mx[son[u][0]];
    if(val[mx[son[u][1]]]>val[mx[u]]) mx[u]=mx[son[u][1]];
}
inline void down(int u)
{
    if(fz[u])
    {
        swap(son[u][0],son[u][1]);
        fz[son[u][0]]^=1;
        fz[son[u][1]]^=1;
        fz[u]=0;
    }
}

inline void rot(int u)
{
    down(fa[u]),down(u);
    int p=fa[u],d=as(u);
    if(!ar(p))
    {
        son[fa[p]][as(p)]=u;
    }
    fa[u]=fa[p];
    fa[p]=u;
    son[p][d]=son[u][!d];
    fa[son[u][!d]]=p;
    son[u][!d]=p;
    up(p),up(u);
}

inline void splay(int u)
{
    int p;
    for(;!ar(u);)
    {
        p=fa[u];
        if(!ar(p))
            as(p)==as(u)?rot(p):rot(u);
        rot(u);
    }
}

inline void acc(int u)
{
    int p,q;
    for(p=0,q=u;q;p=q,q=fa[q])
    {
        splay(q);
        down(q);
        son[q][1]=p;
        up(q);
    }
}

inline void make_root(int u)
{
    acc(u);
    splay(u);
    fz[u]^=1;
}

inline void spl(int u,int v)
{
    make_root(u);
    acc(v);
    splay(v);
}

inline void link(int u,int v)
{
    make_root(u);
    fa[u]=v;
}

inline void cut(int u,int v)
{
    spl(u,v);
    fa[u]=son[v][0]=0;
    up(v);
}

inline int ask(int u,int v)
{
    spl(u,v);
    return val[mx[v]];
}

int main()
{
    int i,j,p,q;
    cin>>n>>m;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&bn[i].from,&bn[i].to,&bn[i].a,&bn[i].b);
    }
    sort(bn+1,bn+m+1);
    for(i=1;i<=n;i++) bcj[i]=i;
    for(i=1;i<=m;i++)
    {
        if(fb(bn[i].from)==fb(bn[i].to))
        {
            if(ask(bn[i].from,bn[i].to)<=bn[i].a)
            {
                continue;
            }
            p=mx[bn[i].to];
            cut(bn[p-n].from,p);
            cut(bn[p-n].to,p);
        }
        mx[i+n]=i+n;
        val[i+n]=bn[i].a;
        link(bn[i].from,i+n);
        link(i+n,bn[i].to);
        bcj[fb(bn[i].from)]=fb(bn[i].to);
        if(fb(1)==fb(n))
            ans=min(ans,ask(1,n)+bn[i].b);
    }
    ans==INF?puts("-1"):printf("%d",ans);
}

你可能感兴趣的:(经典,技巧,LCT)