3669: [Noi2014]魔法森林

3669: [Noi2014]魔法森林

Time Limit: 30 Sec   Memory Limit: 512 MB
Submit: 2089   Solved: 1277
[ 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]


首先按照A类权值为第一关键字排序
我们只需在不断加边的时候维护一棵最小生成树,然后询问(1,N)路径上权值最大的边即可
朴素算法复杂度是O(nm)的

于是使用LCT,加边与删边就变成O(logn)啦
嗯,,,把每条边也看做点,每个点的权值赋为-INF,操作就方便许多啦



询问应当持续执行,,,我们只需保证能从1走到N即可,而不是生成一棵树以后再,,,(我好蠢啊)
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<bitset>
#include<algorithm>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<cmath>
#include<ext/pb_ds/priority_queue.hpp>
using namespace std;

const int maxn = 5E4 + 50;
const int maxm = 3*maxn;
const int INF = ~0U>>1;

struct E{
	int x,y,A,B;
	E(int _x = 0,int _y = 0,int _A = 0,int _B = 0) {x = _x; y = _y; A = _A; B = _B;}
	bool operator < (const E &b) const {return A < b.A;}
}edgs[maxn*2];

int n,m,ans = INF,ch[maxm][2],rev[maxm],key[maxm],fa[maxm],pfa[maxm],Max[maxm],pos[maxm];

void maintain(int x)
{
	Max[x] = key[x]; pos[x] = x;
	if (ch[x][0] && Max[ch[x][0]] > Max[x]) Max[x] = Max[ch[x][0]],pos[x] = pos[ch[x][0]];
	if (ch[x][1] && Max[ch[x][1]] > Max[x]) Max[x] = Max[ch[x][1]],pos[x] = pos[ch[x][1]];
}

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

void pushdown(int x)
{
	if (rev[x]) {
		swap(ch[x][0],ch[x][1]); rev[x] ^= 1;
		if (ch[x][0]) rev[ch[x][0]] ^= 1;
		if (ch[x][1]) rev[ch[x][1]] ^= 1;
	}
}

	stack <int> s;
	
void splay(int x)
{
	for (int now = x; now; now = fa[now]) s.push(now);
	while (!s.empty()) {pushdown(s.top()); s.pop();}
	for (int y; y = fa[x]; rotate(x)) 
		if (fa[y])
			rotate((ch[fa[y]][0] == y)^(ch[y][0] == x)?y:x);
	maintain(x);
}

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

void ChangeRoot(int x) {Access(x); splay(x); rev[x] ^= 1;}

void Join(int x,int y) {ChangeRoot(x); pfa[x] = y;}

void Cut(int x,int y)
{
	ChangeRoot(x); 
	Access(y); splay(y);
	fa[x] = pfa[x] = 0;
	ch[y][0] = 0; maintain(y);
}

int Find_Root(int x)
{
	Access(x); splay(x);
	while (ch[x][0]) x = ch[x][0];
	splay(x); 
	return x;
}

void Query(int k)
{
	int fA = Find_Root(1);
	int fB = Find_Root(n);
	if (fA != fB) return;
	ChangeRoot(1);
	Access(n); splay(n);
	ans = min(ans,Max[n] + edgs[k].A);
}

void Add(int k)
{
	int fx = Find_Root(edgs[k].x);
	int fy = Find_Root(edgs[k].y);
	if (fx != fy) {
		Join(edgs[k].x,n + k); Join(n + k,edgs[k].y);
		return;
	}
	ChangeRoot(edgs[k].x);
	Access(edgs[k].y); splay(edgs[k].y);
	if (Max[ch[edgs[k].y][0]] <= edgs[k].B) return;
	int cut = pos[ch[edgs[k].y][0]];
	E e = edgs[cut-n];
	Cut(e.x,cut); Cut(cut,e.y);
	Join(edgs[k].x,n + k); Join(n + k,edgs[k].y);
	return;
}

int main()
{
	#ifdef DMC
		freopen("forest21.in","r",stdin);
	#endif
	
	cin >> n >> m;
	for (int i = 1; i <= n; i++) Max[i] = key[i] = -INF,pos[i] = i;
	for (int i = 1; i <= m; i++) {
		int x,y,A,B; scanf("%d%d%d%d",&x,&y,&A,&B);
		edgs[i] = E(x,y,A,B);
	}
	sort(edgs + 1,edgs + m + 1);
	for (int i = n + 1; i <= n + m; i++) Max[i] = key[i] = edgs[i-n].B,pos[i] = i;
	for (int i = 1; i <= m; i++) {
		if (edgs[i].x == edgs[i].y) continue;
		Add(i); Query(i);
	}
	if (ans == INF) cout << -1; else cout << ans;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值