洛谷P2387 [NOI2014] 魔法森林 题解
题目链接:P2387 [NOI2014] 魔法森林
题意:每条边有边权
a
,
b
a,b
a,b 两个,求
1
1
1 到
n
n
n 的路径使得所经过的边中
max
?
{
a
}
+
max
?
{
b
}
\max\{a\}+\max\{b\}
max{a}+max{b} 尽可能小
由于下面写了很多解题思路上的东西,导致有些冗长,因此这里先放简化版题解
如果不理解可以跳过这一段简化版,直接看下面的完整版
简化版
因为不方便同时处理
a
,
b
a,b
a,b 两维,我们可以枚举
a
a
a 然后尽可能的选择较小的
b
b
b
这个
b
b
b 的一定在连接
1
1
1 和
n
n
n 的最小生成树上
因此这道题就是个动态加边的LCT裸题了
代码在后面,时间复杂度
O
(
m
log
?
m
)
O(m\log m)
O(mlogm)
完整版
这种“二维”的问题常见的解法就是枚举第一维,然后优化第二维
不知道这个套路也没关系
首先看到这个题意,最大值的和最小,显然我们无法确定这是个多大的值
似乎可以二分?但是再一想,二分
a
+
b
a+b
a+b 肯定是不对的
那先二分
a
a
a 再二分
b
b
b (二分套二分)呢?
看上去似乎也不行,这个
b
b
b 不是很好二分,而且复杂度似乎是
O
(
n
log
?
3
n
)
O(n\log^3n)
O(nlog3n) 的
那二分
a
a
a 呢?好像要对
a
a
a 这一维从小到大排个序,然后在
a
a
a 相等时,第二维也从小到大排序
而每次我们检验
a
a
a 时,都要把在
a
a
a 所在位置(数组中)左边的边都去连连看有没有用什么的
那还不如直接从小到大枚举
a
a
a 呢!
于是我们可以枚举
a
a
a ,然后发现如果要保证
b
b
b 尽可能的小
我们需要在所有
a
’
<
a
a’<a
a’<a 的边中找出一棵生成树
也就是在用
a
a
a 所在位置(数组中)左边的边建出的图中建最小生成森林(因为这个子图不一定连通)
其实我们并不是在为了维护最小生成森林而维护它的
而是在维护包括结点
1
1
1 和
n
n
n 最小生成树的过程中所必需的 qwq
当这个包括结点
1
1
1 和
n
n
n 的最小生成树建出来的时候,我们就可以更新答案了
注意这里是更新答案,不是最终结果,因为最终结果它和
a
a
a 的大小没有直接的关系
举个例子就知道了,a=1,b=998244353 和 a=2,b=1 ,显然是后者更小
那么怎么动态的维护一棵最小生成树呢?我们可以使用LCT
如果不会LCT动态维护最小生成树,可以看看这篇
那么,其实这道题就解决了
也就是枚举
a
a
a ,动态维护最小生成树
时间复杂度
O
(
m
log
?
m
)
O(m\log m)
O(mlogm)
代码如下
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define gc() getchar()
#define pc(a) putchar(a)
#define N (int)(3e5+5)
template<typename T>void read(T &k)
{
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
k=x*f;
}
template<typename T>void write(T k)
{
if(k<0){k=-k;pc('-');}
if(k>9)write(k/10);
pc(k%10+'0');
}
int n,m,ans=INF;
namespace LCT
{
struct Edge{int u,v,a,b;}e[N];
int cmp(Edge a,Edge b){return a.a==b.a?a.b<b.b:a.a<b.a;}
struct node
{
int ch[2],id,mx,w,fa,tag;
}t[N];
#define isroot(x) ((t[t[x].fa].ch[0]!=x)&&(t[t[x].fa].ch[1]!=x))
void pushr(int x)
{
swap(t[x].ch[0],t[x].ch[1]);
t[x].tag^=1;
}
void push_up(int x)
{
t[x].id=x;t[x].mx=t[x].w;
if(t[x].ch[0]&&t[t[x].ch[0]].mx>t[x].mx)
t[x].mx=t[t[x].ch[0]].mx,t[x].id=t[t[x].ch[0]].id;
if(t[x].ch[1]&&t[t[x].ch[1]].mx>t[x].mx)
t[x].mx=t[t[x].ch[1]].mx,t[x].id=t[t[x].ch[1]].id;
}
void push_down(int x)
{
if(t[x].tag)
{
if(t[x].ch[0])pushr(t[x].ch[0]);
if(t[x].ch[1])pushr(t[x].ch[1]);
t[x].tag^=1;
}
}
void push_all(int x)
{
if(!isroot(x))push_all(t[x].fa);
push_down(x);
}
void rotate(int x)
{
int y=t[x].fa;
int z=t[y].fa;
int k=t[y].ch[1]==x;
if(!isroot(y))t[z].ch[t[z].ch[1]==y]=x;
t[x].fa=z;
t[y].ch[k]=t[x].ch[k^1];
t[t[x].ch[k^1]].fa=y;
t[x].ch[k^1]=y;
t[y].fa=x;
push_up(y);
push_up(x);
}
void splay(int x)
{
push_all(x);
while(!isroot(x))
{
int y=t[x].fa;
int z=t[y].fa;
if(!isroot(y))
(t[z].ch[1]==y)^(t[y].ch[1]==x)?rotate(x):rotate(y);
rotate(x);
}
}
void access(int x)
{
for(int y=0;x;y=x,x=t[x].fa)
splay(x),t[x].ch[1]=y,push_up(x);
}
void make_root(int x)
{
access(x);splay(x);
pushr(x);
}
int find_root(int x)
{
access(x);splay(x);
while(t[x].ch[0])push_down(x),x=t[x].ch[0];
splay(x);
return x;
}
void split(int x,int y)
{
make_root(x);
access(y);splay(y);
}
void link(int x,int y)
{
make_root(x);
if(find_root(y)!=x)t[x].fa=y;
}
void cut(int x,int y)
{
make_root(x);
if(find_root(y)==x&&t[y].fa==x&&!t[y].ch[0])
{
t[x].ch[1]=t[y].fa=0;
push_up(x);
}
}
int ck(int x,int y)
{
make_root(x);
return find_root(y)!=x;
}
}
signed main()
{
using namespace LCT;
read(n);read(m);
for(int i=1; i<=m; i++)
{
read(e[i].u);read(e[i].v);
read(e[i].a);read(e[i].b);
}
sort(e+1,e+1+m,cmp);
for(int i=1; i<=m; i++)
{
int idx=n+i,x=e[i].u,y=e[i].v;
t[idx].w=e[i].b;
if(x==y)continue;
if(ck(x,y))
link(x,idx),link(idx,y);
else
{
split(x,y);int now=t[y].id;
if(t[now].mx<=e[i].b)continue;splay(now);
t[t[now].ch[0]].fa=t[t[now].ch[1]].fa=0;
link(x,idx);link(idx,y);
}
if(!ck(1,n))
{
split(1,n);
ans=min(ans,t[n].mx+e[i].a);
}
}
if(ans==INF)write(-1);
else write(ans);pc('\n');
return 0;
}
转载请说明出处
|