「NOI2014」 魔法森林 - 动态树LCT

2024-01-02 07:48
文章标签 动态 森林 魔法 lct noi2014

本文主要是介绍「NOI2014」 魔法森林 - 动态树LCT,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目描述

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

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

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

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

输入格式

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

输出格式

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

分析

如果没有 B B B型守护精灵,那么就是求 1 1 1~ N N N的所有路径中最大边权的最小值。很容易想到用最小生成树解决。现在一条边有两个权值,同样按照Kruskal算法的思路,固定边的一种权值 A i A_i Ai,将其按从小到大排序,然后就可以发现,答案中 B i B_i Bi就一定在以 B i B_i Bi为权值的最小生成树上,于是就可以用LCT维护 B B B的最小生成树。当 1 1 1 N N N连通时就更新答案。对于自环与重边,可以不处理,LCT会自动过滤。似乎还有一种用Spfa的方法,供大家思考。 (LCT也没那么长,压行后百行都不到)

代码

#include <algorithm>
#include <climits>
#include <cstdio>
using namespace std;
const int N=500005,M=100005;
const int V=N+M,INF=INT_MAX>>1;
typedef long long LL;
struct Edge {int u,v,ca,cb;}e[M];
int n,m,fa[N],ans;
bool cmp(Edge a,Edge b) {return a.ca<b.ca;}
int getf(int x) {return fa[x]==x?x:fa[x]=getf(fa[x]);}
int c[V][2],f[V];
int mx[V],v[V],rv[V],st[V];
void init(int x,int p) {mx[x]=v[x]=p;}
bool nroot(int x) {return c[f[x]][0]==x||c[f[x]][1]==x;}
void makerv(int x) {rv[x]^=1;swap(c[x][0],c[x][1]);}
void pushup(int x) {mx[x]=v[x];if (e[mx[c[x][0]]].cb>e[mx[x]].cb) mx[x]=mx[c[x][0]];if (e[mx[c[x][1]]].cb>e[mx[x]].cb) mx[x]=mx[c[x][1]];
}
void pushdown(int x) {if (!x||!rv[x]) return;if (c[x][0]) makerv(c[x][0]);if (c[x][1]) makerv(c[x][1]);rv[x]=0;
}
void pushtg(int x) {int top=0;st[++top]=x;while (f[x]) st[++top]=(x=f[x]);while (top) pushdown(st[top--]);
}
void rotate(int x) {int y=f[x],z=f[y];int k=c[y][1]==x;if (nroot(y)) c[z][c[z][1]==y]=x;f[x]=z;f[y]=x;if (c[x][k^1]) f[c[x][k^1]]=y;c[y][k]=c[x][k^1];c[x][k^1]=y;pushup(y);pushup(x);
}
void splay(int x) {pushtg(x);for (int y,z;y=f[x],z=f[y],nroot(x);rotate(x))if (nroot(y)) rotate((c[y][0]==x)^(c[z][0]==y)?x:y);pushup(x);
}
void access(int x) {for (int y=0;x;splay(x),c[x][1]=y,pushup(x),x=f[y=x]);}
void makert(int x) {access(x);splay(x);makerv(x);}
void split(int x,int y) {makert(x);access(y);splay(y);}
void link(int x,int y) {makert(x);f[x]=y;pushup(y);}
void cut(int x,int y) {split(x,y);f[x]=c[y][0]=0;pushup(y);}
int main() {scanf("%d%d",&n,&m);for (int i=1;i<=m;i++) {int u,v,a,b;scanf("%d%d%d%d",&u,&v,&a,&b);e[i]=(Edge){u,v,a,b};}sort(e+1,e+m+1,cmp);for (int i=1;i<=n;i++) init(i,0),fa[i]=i;for (int i=n+1;i<=n+m;i++) init(i,i-n);ans=INF;for (int i=1;i<=m;i++) {int u=e[i].u,v=e[i].v;int fu=getf(u),fv=getf(v);if (fu!=fv) link(u,i+n),link(i+n,v),fa[fu]=fv;else {split(u,v);int num=mx[v];if (e[num].cb>e[i].cb) {cut(e[num].u,num+n);cut(num+n,e[num].v);link(u,i+n);link(i+n,v);}}if (getf(1)==getf(n)) {split(1,n);ans=min(ans,e[i].ca+e[mx[n]].cb);}}if (ans==INF) printf("-1");else printf("%d",ans);return 0;
}

这篇关于「NOI2014」 魔法森林 - 动态树LCT的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/561784

相关文章

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

LeetCode:64. 最大正方形 动态规划 时间复杂度O(nm)

64. 最大正方形 题目链接 题目描述 给定一个由 0 和 1 组成的二维矩阵,找出只包含 1 的最大正方形,并返回其面积。 示例1: 输入: 1 0 1 0 01 0 1 1 11 1 1 1 11 0 0 1 0输出: 4 示例2: 输入: 0 1 1 0 01 1 1 1 11 1 1 1 11 1 1 1 1输出: 9 解题思路 这道题的思路是使用动态规划

vue2实践:el-table实现由用户自己控制行数的动态表格

需求 项目中需要提供一个动态表单,如图: 当我点击添加时,便添加一行;点击右边的删除时,便删除这一行。 至少要有一行数据,但是没有上限。 思路 这种每一行的数据固定,但是不定行数的,很容易想到使用el-table来实现,它可以循环读取:data所绑定的数组,来生成行数据,不同的是: 1、table里面的每一个cell,需要放置一个input来支持用户编辑。 2、最后一列放置两个b

Windows下php扩展开发c++动态库

PHP扩展开发,从零了解到初步完成一个小项目,经过三天的仔细研究,现整理如下 一、需求介绍 PHP扩展开发,调用自己之前的c++动态库,完成功能 二、项目之前 系统:windows xp  开发工具:vs 2008 web环境:apache2.4  PHP5.3.29-VC9-ts-x86 aphach和PHP 环境之前已经搭建完成 PHP源码:去官网http://www.php.n

LeetCode:3177. 求出最长好子序列 II 哈希表+动态规划实现n*k时间复杂度

3177. 求出最长好子序列 II 题目链接 题目描述 给你一个整数数组 nums 和一个非负整数k 。如果一个整数序列 seq 满足在下标范围 [0, seq.length - 2] 中 最多只有 k 个下标i满足 seq[i] != seq[i + 1] ,那么我们称这个整数序列为好序列。请你返回 nums中好子序列的最长长度。 实例1: 输入:nums = [1,2,1,1,3],

vue2实践:第一个非正规的自定义组件-动态表单对话框

前言 vue一个很重要的概念就是组件,作为一个没有经历过前几代前端开发的我来说,不太能理解它所带来的“进步”,但是,将它与后端c++、java类比,我感觉,组件就像是这些语言中的类和对象的概念,通过封装好的组件(类),可以通过挂载的方式,非常方便的调用其提供的功能,而不必重新写一遍实现逻辑。 我们常用的element UI就是由饿了么所提供的组件库,但是在项目开发中,我们可能还需要额外地定义一