999. 魔法森林

题目链接

999. 魔法森林

为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐士。

魔法森林可以被看成一个包含 \(n\) 个节点 \(m\) 条边的无向图,节点标号为 \(1,2,3,…,n\),边标号为 \(1,2,3,…,m\)

初始时小 E 同学在 \(1\) 号节点,隐士则住在 \(n\) 号节点。

小 E 需要通过这一片魔法森林,才能够拜访到隐士。

魔法森林中居住了一些妖怪。

每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。

幸运的是,在 \(1\) 号节点住着两种守护精灵:A 型守护精灵与 B 型守护精灵。

小 E 可以借助它们的力量,达到自己的目的。

只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。

具体来说,无向图中的每一条边 \(e_i\) 包含两个权值 \(a_i\)\(b_i\)

若身上携带的 A 型守护精灵个数不少于 \(a_i\) ,且 B 型守护精灵个数不少于 \(b_i\) ,这条边上的妖怪就不会对通过这条边的人发起攻击。

当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向 小 E 发起攻击,他才能成功找到隐士。

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

守护精灵的总个数为 A 型守护精灵的个数与 B 型守护精灵的个数之和。

输入格式

\(1\) 行包含两个整数 \(n,m\),表示无向图共有 \(n\) 个节点,\(m\) 条边。

接下来 \(m\) 行,第 \(i+1\) 行包含 \(4\) 个正整数 \(X_i,Y_i,a_i,b_i\),描述第 \(i\) 条无向边。 其中 \(X_i\)\(Y_i\) 为该边两个端点的标号,\(a_i\)\(b_i\) 的含义如题所述。

注意数据中可能包含重边与自环。

输出格式

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

数据范围

image

输入样例1:

4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17

输出样例1:

32

输入样例2:

3 1
1 2 1 1

输出样例2:

-1

样例解释

样例1:
image

如果小 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:
小 E 无法从 1 号节点到达 3 号节点,故输出-1。

解题思路

LCT

由于一条边有两个属性 \(a,b\),答案显然是某两个属性的组合,要求两个属性 \(A,B\),且找到一条路径使得路径上所有的 \(a_i\leq A,b_i\leq B\),这样的 \(A+B\) 最小,不妨枚举 \(A=a_i\),即先将所有边按 \(a\) 排序,对于当前边 \(i\),即在所有前面构成的边中形成一个图中找到 \(1\sim n\) 的路径的最大 \(b_i\) 的最小值,不妨用并查集维护边的信息,当两个点不在同一个集合时加边,否则会形成一个环,直觉上应该删除环上边权最大的边,\(\color{red}{为什么?}\)假设这条边权最大的边最后要用到,但可以通过走环上的另外一条路径使得这样的 \(B\) 不会更差。这样便对应 LCT 的加边和删边操作,另外有一点需要注意:LCT 维护的都是点权,而这里都是边权,\(\color{red}{怎么办呢?}\)有一个比较好的拆边操作:在每条边中间插入一个点,将边权赋为该点,而其他点的点权都为 \(0\),这是由于找路径上权值最大的点时一定不会找到权值为 \(0\) 的点,同时这样整棵树的结构不变,因为每次操作都涉及中间点和两个端点连接的边,即对应没有插点时的边

  • 时间复杂度:\(O(m\times log(n+m))\)

代码

// Problem: 魔法森林
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/1001/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=5e4+5,M=1e5+5,inf=0x3f3f3f3f;
int n,m,fa[N+M],stk[N+M];
struct Edge
{
	int x,y,a,b;
	bool operator<(const Edge &o)const
	{
		return a<o.a;
	}
}e[M];
struct Tr
{
	int s[2],p,v;
	int rev,mx;
}tr[N+M];
int find(int x)
{
	return x==fa[x]?x:fa[x]=find(fa[x]);
}
void pushup(int x)
{
	tr[x].mx=x;
	for(int i=0;i<2;i++)
		if(tr[tr[x].mx].v<tr[tr[tr[x].s[i]].mx].v)
			tr[x].mx=tr[tr[x].s[i]].mx;
}
void pushrev(int x)
{
	swap(tr[x].s[0],tr[x].s[1]);
	tr[x].rev^=1;
}
void pushdown(int x)
{
	if(tr[x].rev)
	{
		pushrev(tr[x].s[0]),pushrev(tr[x].s[1]);
		tr[x].rev=0;
	}
}
bool isroot(int x)
{
	return tr[tr[x].p].s[0]!=x&&tr[tr[x].p].s[1]!=x;
}
void rotate(int x)
{
	int y=tr[x].p,z=tr[y].p;
	int k=tr[y].s[1]==x;
	if(!isroot(y))tr[z].s[tr[z].s[1]==y]=x;
	tr[x].p=z;
	tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].p=y;
	tr[x].s[k^1]=y,tr[y].p=x;
	pushup(y),pushup(x);
}
void splay(int x)
{
	int top=0,r=x;
	stk[++top]=r;
	while(!isroot(r))stk[++top]=r=tr[r].p;
	while(top)pushdown(stk[top--]);
	while(!isroot(x))
	{
		int y=tr[x].p,z=tr[y].p;
		if(!isroot(y))
			if((tr[y].s[1]==x)^(tr[z].s[1]==y))rotate(x);
			else
				rotate(y);
		rotate(x);
	}
}
void access(int x)
{
	int z=x;
	for(int y=0;x;y=x,x=tr[x].p)
	{
		splay(x);
		tr[x].s[1]=y,pushup(x);
	}
	splay(z);
}
void makeroot(int x)
{
	access(x);
	pushrev(x);
}
int findroot(int x)
{
	access(x);
	while(tr[x].s[0])x=tr[x].s[0];
	splay(x);
	return x;
}
void split(int x,int y)
{
	makeroot(x);
	access(y);
}
void link(int x,int y)
{
	makeroot(x);
	if(findroot(y)!=x)tr[x].p=y;
}
void cut(int x,int y)
{
	makeroot(x);
	if(findroot(y)==x&&tr[y].p==x&&!tr[y].s[0])
	{
		tr[x].s[1]=tr[y].p=0;
		pushup(x);
	}
}
int main()
{
    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+1+m);
    for(int i=1;i<=n+m;i++)
    {
    	fa[i]=i;
    	if(i>n)tr[i].v=e[i-n].b;
    }
    int res=inf;
    for(int i=1;i<=m;i++)
    {
    	int x=e[i].x,y=e[i].y,a=e[i].a,b=e[i].b;
    	if(find(x)==find(y))
    	{
    		split(x,y);
    		int mx_id=tr[y].mx;
    		if(tr[mx_id].v>b)
    		{
    			cut(e[mx_id-n].x,mx_id),cut(mx_id,e[mx_id-n].y);
    			link(x,i+n),link(i+n,y);
    		}
    	}
    	else
    	{
    		fa[find(x)]=find(y);
    		link(x,i+n),link(i+n,y);
    	}
    	if(find(1)==find(n))split(1,n),res=min(res,a+tr[tr[n].mx].v);
    }
    if(res==inf)puts("-1");
    else
    	printf("%d",res);
    return 0;
}
posted @ 2022-11-16 17:02  zyy2001  阅读(62)  评论(0编辑  收藏  举报