题目链接
为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐士。
魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 1,2,3,…,n,边标号为 1,2,3,…,m。
初始时小 E 同学在 1 号节点,隐士则住在 n 号节点。
小 E 需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。
每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。
幸运的是,在 1 号节点住着两种守护精灵:A 型守护精灵与 B 型守护精灵。
小 E 可以借助它们的力量,达到自己的目的。
只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。
具体来说,无向图中的每一条边 ei 包含两个权值 ai 与 bi 。
若身上携带的 A 型守护精灵个数不少于 ai ,且 B 型守护精灵个数不少于 bi ,这条边上的妖怪就不会对通过这条边的人发起攻击。
当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向 小 E 发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。
守护精灵的总个数为 A 型守护精灵的个数与 B 型守护精灵的个数之和。
输入格式
第 1 行包含两个整数 n,m,表示无向图共有 n 个节点,m 条边。
接下来 m 行,第 i+1 行包含 4 个正整数 Xi,Yi,ai,bi,描述第 i 条无向边。 其中 Xi 与 Yi 为该边两个端点的标号,ai 与 bi 的含义如题所述。
注意数据中可能包含重边与自环。
输出格式
输出一行一个整数:如果小 E 可以成功拜访到隐士,输出小 E 最少需要携带的守护精灵的总个数;如果无论如何小 E 都无法拜访到隐士,输出“-1”(不含引号)。
数据范围

输入样例1:
输出样例1:
输入样例2:
输出样例2:
样例解释
样例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:
小 E 无法从 1 号节点到达 3 号节点,故输出-1。
解题思路
LCT
由于一条边有两个属性 a,b,答案显然是某两个属性的组合,要求两个属性 A,B,且找到一条路径使得路径上所有的 ai≤A,bi≤B,这样的 A+B 最小,不妨枚举 A=ai,即先将所有边按 a 排序,对于当前边 i,即在所有前面构成的边中形成一个图中找到 1∼n 的路径的最大 bi 的最小值,不妨用并查集维护边的信息,当两个点不在同一个集合时加边,否则会形成一个环,直觉上应该删除环上边权最大的边,为什么?假设这条边权最大的边最后要用到,但可以通过走环上的另外一条路径使得这样的 B 不会更差。这样便对应 LCT 的加边和删边操作,另外有一点需要注意:LCT 维护的都是点权,而这里都是边权,怎么办呢?有一个比较好的拆边操作:在每条边中间插入一个点,将边权赋为该点,而其他点的点权都为 0,这是由于找路径上权值最大的点时一定不会找到权值为 0 的点,同时这样整棵树的结构不变,因为每次操作都涉及中间点和两个端点连接的边,即对应没有插点时的边
- 时间复杂度:O(m×log(n+m))
代码
#include <bits/stdc++.h>
#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;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2021-11-16 华东交通大学2019年ACM 双基 程序设计竞赛
2021-11-16 矩阵乘法