【bzoj3669】[Noi2014]魔法森林

2 篇文章 0 订阅

3669: [Noi2014]魔法森林

Time Limit: 30 Sec   Memory Limit: 512 MB
Submit: 2884   Solved: 1806
[ Submit][ Status][ Discuss]

Description

为了得到书法大家的真传,E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被一个包含个N节点M条边的无向图节点标号为1..N边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。

魔法森林中居住了一些妖怪。每当有人经过一条边时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵A型守护精灵与B型守护精灵小E可以借助它们的力量,达到自己的目的

只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说无向图中的每一条边Ei包含两个权值Ai与Bi身上携带的A型守护精灵个数不少于Ai且B型守护精灵个数不少于Bi这条边上的妖怪不会对通过这条边人发起攻击当且仅当通过魔法森林的过程中没有任意一条边妖怪小E发起攻击他才能成功找到隐士。

由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数守护精灵总个数A型守护精灵的个数与B型守护精灵的个数之和。

Input

第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。

Output

输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。

Sample Input

【输入样例1】
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17





【输入样例2】


3 1
1 2 1 1



Sample Output

【输出样例1】

32
【样例说明1】
如果小E走路径1→2→4,需要携带19+15=34个守护精灵;
如果小E走路径1→3→4,需要携带17+17=34个守护精灵;
如果小E走路径1→2→3→4,需要携带19+17=36个守护精灵;
如果小E走路径1→3→2→4,需要携带17+15=32个守护精灵。
综上所述,小E最少需要携带32个守护精灵。



【输出样例2】


-1
【样例说明2】
小E无法从1号节点到达3号节点,故输出-1。

HINT

2<=n<=50,000


0<=m<=100,000




1<=ai ,bi<=50,000

Source

[ Submit][ Status][ Discuss]





GG。。。。


这题一开始其实就想到了思路,但是由于不会LCT的边权维护。。还是去百度了一下


言归正传


这一题是一道痕经典的LCT维护最小生成树的题目


首先我们枚举每一个a,依次把a值为当前枚举的a的边加入图中

用LCT维护图上的以b为权值的最小生成树森林(注:LCT维护边权可以每一个边建成点,于是转变成维护点权)

这样做完一遍后查询从1到n的路径上的最大值,更新答案


就没啦。。。


但是可能有个问题:如何维护最小生成树森林呢?


我们这样考虑:

对于加入一条边,如果边的两个端点不为同一棵树内,那么直接加进去就好了;

但是如果两个端点在同一棵树内,那么我们加进去这条边势必会产生一个环,这时候我们删掉环上的最大边,即可满足最小生成树的性质


嗯大概就这样


PS.蒟蒻access里忘打maintain调了两个小时。。。


代码:

#include<cstdio>
#include<vector>
#include<queue>
#include<ctime>
#include<algorithm>
#include<cstdlib>
#include<stack>
#include<cstring>
#include<cmath>
using namespace std;

typedef long long LL;

const int INF = 2147483647;
const int maxn = 200000;

struct data{
	int u,v,a,b;
}e[maxn];

int n,m,tot,ans,p;
int pfa[maxn];
int id[maxn],val[maxn],ch[maxn][2],fa[maxn];
bool rev[maxn],flag,exist[maxn];

inline void maintain(int o)
{
	id[o] = o;
	if (val[id[o]] < val[id[ch[o][1]]]) id[o] = id[ch[o][1]];
	if (val[id[o]] < val[id[ch[o][0]]]) id[o] = id[ch[o][0]];
}

inline void rotate(int x)
{
	int o = fa[x],y = fa[o];
	pfa[x] = pfa[o]; pfa[o] = 0;
	int d = ch[o][1] == x ? 0 : 1;
	ch[o][d ^ 1] = ch[x][d]; maintain(o);
	if (ch[x][d]) fa[ch[x][d]] = o;
	ch[x][d] = o; maintain(x);
	fa[o] = x; fa[x] = y;
	if (y) ch[y][ch[y][1] == o] = x , maintain(y);
}

inline void reverse(int o)
{
	rev[o] ^= 1;
	swap(ch[o][1],ch[o][0]);
}

inline void pushdown(int o)
{
	if (rev[o])
	{
		rev[o] = 0;
		if (ch[o][1]) reverse(ch[o][1]);
		if (ch[o][0]) reverse(ch[o][0]);
	}
}

inline void pushdown_root(int x)
{
	if (fa[x]) pushdown_root(fa[x]);
	pushdown(x);
}

inline void splay(int x)
{
	pushdown_root(x);
	for (int y = fa[x]; y; rotate(x) , y = fa[x])
		if (fa[y]) rotate((ch[y][1] == x) ^ (ch[fa[y]][1] == y) ? x : y);
}

inline void access(int x)
{
	for (int u = x,v = 0; u; v = u,u = pfa[u])
	{
		splay(u);
		if (ch[u][1]) fa[ch[u][1]] = 0 , pfa[ch[u][1]] = u;
		if (v) fa[v] = u , pfa[v] = 0;
		ch[u][1] = v;
		maintain(u);
	}
}

inline void change(int u)
{
	access(u);
	splay(u);
	reverse(u);
}

inline void cut(int u,int v)
{
	change(u);
	access(v);
	splay(v);
	if (ch[v][0]) fa[ch[v][0]] = 0 , ch[v][0] = 0;
	maintain(v);
}

inline void join(int u,int v)
{
	change(v);
	pfa[v] = u;
	access(v);
}

inline int find(int u)
{
	access(u);
	splay(u);
	while (ch[u][0]) u = ch[u][0];
	return u;
}

inline int query(int u,int v)
{
	change(u);
	access(v);
	splay(v);
	return id[v];
}

inline LL getint()
{
    LL ret = 0,f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        ret = ret * 10 + c - '0',c = getchar();
    return ret * f;
}

inline bool cmp(data a,data b)
{
	return a.a < b.a;
}

int main()
{
	n = getint(); m = getint();
	for (int i = 1; i <= m; i++) e[i] = (data){getint(),getint(),getint(),getint()};
	sort(e + 1,e + m + 1,cmp);
	ans = INF;
	tot = n;
	for (int i = 1; i <= m; i++)
	{
		int u = e[i].u,v = e[i].v,w = e[i].b;
		val[++tot] = w;
		if (u != v) 
		{
			if (find(u) != find(v))
			{
				join(u,tot); 
				join(tot,v);
			}
			else
			{
				int pos = query(u,v);
				if (val[pos] > w)
				{
					cut(pos,e[pos - n].u);
					cut(e[pos - n].v,pos);
					join(u,tot); 
					join(tot,v);
				}
			}
		}
		if (e[i].a != e[i + 1].a)
		{
			if (find(1) != find(n)) continue;
			int pos = query(1,n);
			ans = min(ans,val[pos] + e[i].a);
		}
	}
	if (ans == INF) printf("-1\n");
	else printf("%d\n",ans);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值