999. 魔法森林

题目链接

999. 魔法森林

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

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

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

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

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

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

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

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

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

具体来说,无向图中的每一条边 ei 包含两个权值 aibi

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

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

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

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

输入格式

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

接下来 m 行,第 i+1 行包含 4 个正整数 Xi,Yi,ai,bi,描述第 i 条无向边。 其中 XiYi 为该边两个端点的标号,aibi 的含义如题所述。

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

输出格式

输出一行一个整数:如果小 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,且找到一条路径使得路径上所有的 aiA,biB,这样的 A+B 最小,不妨枚举 A=ai,即先将所有边按 a 排序,对于当前边 i,即在所有前面构成的边中形成一个图中找到 1n 的路径的最大 bi 的最小值,不妨用并查集维护边的信息,当两个点不在同一个集合时加边,否则会形成一个环,直觉上应该删除环上边权最大的边,假设这条边权最大的边最后要用到,但可以通过走环上的另外一条路径使得这样的 B 不会更差。这样便对应 LCT 的加边和删边操作,另外有一点需要注意:LCT 维护的都是点权,而这里都是边权,有一个比较好的拆边操作:在每条边中间插入一个点,将边权赋为该点,而其他点的点权都为 0,这是由于找路径上权值最大的点时一定不会找到权值为 0 的点,同时这样整棵树的结构不变,因为每次操作都涉及中间点和两个端点连接的边,即对应没有插点时的边

  • 时间复杂度:O(m×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; }

__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/16896559.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2021-11-16 华东交通大学2019年ACM 双基 程序设计竞赛
2021-11-16 矩阵乘法
点击右上角即可分享
微信分享提示