[BZOJ 3669][NOI 2014]魔法森林(Link-Cut Tree)

题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3669

记得四个月之前的NOI同步赛,我还只会玩脚丫子。。。。

记得我当时看到这个题整个人就吓傻了,完全不知道怎么做,然后NOI同步赛就这样爆零了。。。

如今我学了LCT这个神器,再看这个题,感觉不再那么难了。

其实这个题的标准解法是SPFA,改得完全认不出来的SPFA。

orz太神了,完全没见识过这么神犇的SPFA,NOIP 2014考了SPFA,NOI 2014也考了SPFA,原来SPFA也能玩出这么多花样。

我只会用LCT暴力这题,我会暴力我自豪。然后我orz了hzwer的本题lct版本代码和bin神的lct模板,自己yy乱弄了下过了这个题。

下面是我的思路,如有错误请不吝指教。

first of all,我们要构造下这题的LCT,在这题中,图有n个点m条边,于是lct里,下标在[1,n]的结点都是图中的点,下标在(n,m+n]中的结点都是图中的边,只有边对应的结点有权值,这个权值就是对应的边的b值。

然后我们用类似kruscal的方法,将每个边按照关键字a值升序排序,然后搞个并查集,维护图的连通性。

接着,我们像搞最小生成树一样,按a值从小到大不断地加边,维护并查集,加一条边时,要同时在LCT里连接这条边的两个点u和v,具体做法是先连接u和边对应的结点,再把边对应的结点和v相连。

在这个过程中,如果出现遍历到一条边u-v时,u-v本身是联通的,那么我们不加此边,但是要看LCT中u到v路径上的最大权值是不是大于这条边的b值,若是的话,则需要断掉u到v路径上最大权值的那条边的连接,加入这条边。

最后,答案就是min(a+LCT中起点对应结点到终点对应结点路径上的最大权值之和)。

PS:忍不住吐槽下,我第一次WA掉居然是因为splay操作最后忘了上传标记,orzorz。。。。。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 150050
#define MAXE 100005
#define INF 0x3f3f3f3f

using namespace std;

struct edge
{
    int u,v,a,b;
}edges[MAXE];

int n,m,nCount=0,ans=INF;
int f[MAXN];
int ch[MAXN][2],fa[MAXN];
int val[MAXN],maxv[MAXN]; //该点值的标记、该点对应区间的最大值对应结点的下标
bool rev[MAXN]; //翻转标记
bool isRoot[MAXN]; //根节点标记

int findSet(int x)
{
    if(f[x]==x) return x;
    return f[x]=findSet(f[x]);
}

bool operator<(edge a,edge b)
{
    return a.a<b.a;
}
//Start Of Splay
void updateRev(int r) //更新结点r的rev标记
{
    if(!r) return;
    swap(ch[r][0],ch[r][1]);
    rev[r]^=1;
}

void pushdown(int r) //更新结点r的rev标记
{
    if(rev[r])
    {
        updateRev(ch[r][0]);
        updateRev(ch[r][1]);
        rev[r]=0;
    }
}

void pushup(int r) //更新maxv域
{
    int lc=ch[r][0],rc=ch[r][1];
    maxv[r]=r;
    if(val[maxv[lc]]>val[maxv[r]]) maxv[r]=maxv[lc];
    if(val[maxv[rc]]>val[maxv[r]]) maxv[r]=maxv[rc];
}

void rotate(int x) //将x旋到上面一层去
{
    int y=fa[x],kind=ch[y][1]==x;
    ch[y][kind]=ch[x][!kind];
    fa[ch[y][kind]]=y;
    fa[x]=fa[y];
    fa[y]=x;
    ch[x][!kind]=y;
    if(isRoot[y]) //y是根节点,则需要更新根节点标记
    {
        isRoot[y]=false;
        isRoot[x]=true;
    }
    else
        ch[fa[x]][ch[fa[x]][1]==y]=x;
    pushup(y);
}

void P(int r) //维护r和它的祖先们
{
    if(!isRoot[r]) P(fa[r]);
    pushdown(r);
}

void splay(int r) //将r旋到根节点上去
{
    P(r);
    while(!isRoot[r])
    {
        int f=fa[r],ff=fa[f]; //父结点、祖父结点
        if(isRoot[f])
            rotate(r);
        else if((ch[ff][1]==f)==(ch[f][1]==r)) //r、r的父亲、r的祖父三点共线
            rotate(f),rotate(r);
        else
            rotate(r),rotate(r);
    }
    pushup(r);
}
//End Of Splay
//Start Of Link-Cut Tree
int access(int x) //打通x到根节点的偏爱路径
{
    int y=0;
    do
    {
        splay(x);
        isRoot[ch[x][1]]=true;
        isRoot[ch[x][1]=y]=false;
        pushup(x);
        x=fa[y=x];
    }while(x);
    return y;
}

void memroot(int r) //使r成为所在树的根
{
    access(r);
    splay(r);
    updateRev(r);
}

void link(int u,int v) //link操作
{
    memroot(u);
    fa[u]=v;
}

void cut(int u,int v) //cut操作
{
    memroot(u);
    access(v);
    splay(v);
    fa[ch[v][0]]=fa[v];
    fa[v]=0;
    isRoot[ch[v][0]]=true;
    ch[v][0]=0;
    pushup(v);
}
//End Of Link-Cut Tree

int query(int x,int y) //求点x到y之间路径上的最大值
{
    memroot(x);
    access(y);
    splay(y);
    return maxv[y];
}

void solve(int k) //如果边k链接的两边的点u和v本身联通,而且u到v路径上的最大的b值比边k的b值大,则用边k去代替点u到v的路径
{
    int u=edges[k].u,v=edges[k].v,w=edges[k].b;
    int t=query(u,v);
    if(w<val[t])
    {
        cut(edges[t-n].u,t);
        cut(edges[t-n].v,t);
        link(u,k+n);
        link(v,k+n);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n+m;i++) isRoot[i]=true;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
        scanf("%d%d%d%d",&edges[i].u,&edges[i].v,&edges[i].a,&edges[i].b);
    sort(edges+1,edges+m+1);
    for(int i=1;i<=m;i++)
    {
        val[n+i]=edges[i].b;
        maxv[n+i]=n+i;
    }
    for(int i=1;i<=m;i++) //进行类似于kruscal的操作,一边维护连通性,一边往lct里面加边
    {
        int u=edges[i].u,v=edges[i].v,w=edges[i].b;
        int rootu=findSet(u),rootv=findSet(v);
        if(rootu!=rootv)
        {
            f[rootu]=rootv;
            link(u,n+i);
            link(v,n+i);
        }
        else solve(i);
        if(findSet(1)==findSet(n)) //起点到终点之间已经联通了
            ans=min(ans,val[query(1,n)]+edges[i].a);
    }
    if(ans!=INF) printf("%d\n",ans);
    else printf("-1");
    return 0;
}



时间: 2024-08-07 19:10:03

[BZOJ 3669][NOI 2014]魔法森林(Link-Cut Tree)的相关文章

【bzoj 3669】[Noi2014]魔法森林

Description 为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士.魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M.初始时小E同学在号节点1,隐士则住在号节点N.小E需要通过这一片魔法森林,才能够拜访到隐士. 魔法森林中居住了一些妖怪.每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击.幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵.小E可以借助它们的力量,达到自己的目的. 只要小E带上足够多的守护精灵,妖怪们

NOI 2014 魔法森林(BZOJ 3669) 题解

对边按a权值排序,按b权值建LCT,按排序后的顺序依次加边.如果加边后形成环则删除环上最大的边.如果起点终点联通则更新答案. 1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,n) for(int i=0;i<n;++i) 4 const int MAXN=50000+5; 5 const int MAXM=100000+5; 6 const int INF=~0U>>1; 7 struct Node

【BZOJ 3669】 [Noi2014]魔法森林 LCT维护动态最小生成树

这道题看题意是在求一个二维最小瓶颈路,唯一可行方案就是枚举一维在这一维满足的条件下使另一维最小,那么我们就把第一维排序利用A小的边在A大的情况下仍成立来动态加边维护最小生成树. #include <cstdio> #include <algorithm> namespace Pre{ inline void read(int &sum){ register char ch=getchar(); for(sum=0;ch<'0'||ch>'9';ch=getcha

Link Cut Tree学习笔记

从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子树信息 小结 动态树问题和Link Cut Tree 动态树问题是一类要求维护一个有根树森林,支持对树的分割, 合并等操作的问题. Link Cut Tree(林可砍树?简称LCT)是解决这一类问题的一种数据结构. 一些无聊的定义 Link Cut Tree维护的是动态森林中每棵树的任意链剖分. P

link cut tree 入门

鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. #include<cstdio> #include<cstring> #include<cctype> #include<algorithm> #include<queue> using namespace std; #define rep(i,s,

Link Cut Tree(无图慎入)

类似树链剖分(其实直接记住就可以了),提前放代码 1 #include<cstdio> 2 #include<cstdlib> 3 #include<iostream> 4 #include<algorithm> 5 #include<cstring> 6 #include<climits> 7 #include<cmath> 8 #define N (int)(3e5+5) 9 using namespace std;

Codeforces Round #339 (Div. 2) A. Link/Cut Tree

A. Link/Cut Tree Programmer Rostislav got seriously interested in the Link/Cut Tree data structure, which is based on Splay trees. Specifically, he is now studying the expose procedure. Unfortunately, Rostislav is unable to understand the definition

AC日记——【模板】Link Cut Tree 洛谷 P3690

[模板]Link Cut Tree 思路: LCT模板: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 300005 int n,m,val[maxn]; int top,ch[maxn][2],f[maxn],xr[maxn],q[maxn],rev[maxn]; inline void in(int &now) { int if_z=1;now=0; char Cget=getchar(); while

bzoj2049 [Sdoi2008]Cave 洞穴勘测 link cut tree入门

link cut tree入门题 首先说明本人只会写自底向上的数组版(都说了不写指针.不写自顶向下QAQ……) 突然发现link cut tree不难写... 说一下各个函数作用: bool isroot(int x):判断x是否为所在重链(splay)的根 void down(int x):下放各种标记 void rotate(int x):在x所在重链(splay)中将x旋转到fa[x]的位置上 void splay(int x):在x坐在重链(splay)中将x旋转到根 void acce