前置知识:
思路:
因为有两个条件,所以我们可以类似dp那么先固定一个,再跑spfa求瓶颈路。
同时很明显可以二分优化。(当然,这还不够优秀)
时间复杂度大致为
下面提供两种正解:
- 动态加点
- 用 求瓶颈生成树
1.
我们以b为第一关键字排序边,然后维护最小生成树。
对于a呢,因为多加一条边,所以只有边的两个端点可以再贡献1~n的瓶颈路。
每次加完边后,答案就为刚加边的b值+1~n的瓶颈路长度。
代码by ZZY大佬:
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<ctype.h>
using namespace std;
struct node3
{
int id,d;
friend bool operator <(node3 x,node3 y)
{
return x.d>y.d;//实际上是小根堆
}
};
priority_queue<node3> f;//堆
int n,m,len=0,ans=2147483647;
struct node1{int x,y,z1,z2;} d[100010];
struct node2{int x,y,z,next;} a[200010];
int last[50010],dis[50010];
bool bz[50010];
inline char getc()//fread优化
{
static char buf[1<<18],*fs,*ft;
return(fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}
inline int read()
{
char ch=getc(),f=1;
int x=0;
for(;!isgraph(ch);ch=getc());
if(ch=='-') f=-1,ch=getc();
for(;isdigit(ch);ch=getc())
x=((x+(x<<2))<<1)+(ch^0x30);
return x*f;
}
bool cmp(node1 x,node1 y)
{
return x.z1<y.z1;
}
void ins(int x,int y,int z)
{
a[++len]=(node2){x,y,z,last[x]}; last[x]=len;
}
int dijkstra(int st1,int st2)//不完全的dijkstra,同学们可以手打堆来保证堆顶的dis最优。这样每个点就只用进一次堆了
{
f.push((node3){st1,dis[st1]});
f.push((node3){st2,dis[st2]});
while(!f.empty())
{
int x=f.top().id;
f.pop();
for(int i=last[x];i;i=a[i].next)
{
int y=a[i].y;
if(dis[y]>max(dis[x],a[i].z))
{
dis[y]=max(dis[x],a[i].z);
if(!bz[y]) f.push((node3){y,dis[y]}),bz[y]=true;
}
}
bz[x]=false;
}
return dis[n];
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
d[i].x=read(),d[i].y=read(),d[i].z1=read(),d[i].z2=read();
sort(d+1,d+m+1,cmp);
memset(dis,63,sizeof(dis));
dis[1]=0;
bool flag=false;
for(int i=1;i<=m;i++)
{
if(d[i].z1>=ans) break;
if(d[i].x==d[i].y) continue;
ins(d[i].x,d[i].y,d[i].z2);
ins(d[i].y,d[i].x,d[i].z2);
if(d[i].x==1||d[i].y==1) flag=true;
if(flag) ans=min(ans,d[i].z1+dijkstra(d[i].x,d[i].y));//
}
printf("%d",ans<(int)1e9?ans:-1);
}
提问: 1. 为什么 不用每次都
2. 如果加的这题边对瓶颈路没有贡献会不会影响答案。
回答:
1.每次 整个图都要重跑,复杂度太大。为了保证正确性,加两个端点,使得这条边对图有贡献就行。
2.分两种情况讨论:
(1):这条边对瓶颈路有贡献
这样的话,显然直接加就行。
(2):这条边对瓶颈路无贡献
这种情况说明这条边没有之前的边优秀,因为每加一条边就更新一下答案,所以之前就已经更新了较优值,对结果无影响。
2.我们要用生成树做。
因为b的上限越大,边越多。(废话)
所以我们可以按边的b sort,再求a的瓶颈生成树。
引理:一个无向图的最小生成树一定为瓶颈生成树。(但瓶颈生成树不一定为最小生成树)
证明:可用反证法。假设最小生成树不为瓶颈生成树,设最小生成树的最大边长度为a,瓶颈路生成树的最大边长度为b,则有a>b,那么瓶颈生成树的每一条边都小于a,我们把长度为a的边(端点为x,y)断开,换成瓶颈生成树中连接x,y所属联通块的边,那么生成树的大小更小,与最小生成树的定义矛盾,故原命题成立。
所以我们用
维护a的最小生成树就行。
本题需要动态加边,所以要用LCT.
因为我们在找1~n的瓶颈路长度时,不能保证1 ~ n的路径的深度严格递增,所以要换根(
),LCT就是有根树。
又因为要算边权,但把边权放给子节点的方法,由于换根而不能做。所以我们定义多m个点,把边权压到这些点上,其他点无权。那么我们就把求边权转化为了求点权。
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
#define lc tr[x].son[0]
#define rc tr[x].son[1]
using namespace std;
const int N=15e4+10,M=1e5+10;//N的范围需注意
void qr(int &x)
{
char c=g;bool v=x=0;
while(!(isdigit(c)||c=='-'))c=g;//isdigit返回bool值表示是否为十进制中的字符.isdigit(c)同'0'<=c&&c<='9'
if(c=='-')v=1,c=g;
while(isdigit(c))x=x*10+c-'0',c=g;
if(v)x=-x;
}
struct edge{int x,y,a,b;}a[M];//边
bool cmp(edge x,edge y){return x.b<y.b;}
int d[N];
struct node{int f,son[2],p;bool v;}tr[N];//p为子树内d的最大值的标号
void update(int x)
{
tr[x].p=x;
if(lc&&d[tr[x].p]<d[tr[lc].p])tr[x].p=tr[lc].p;
if(rc&&d[tr[x].p]<d[tr[rc].p])tr[x].p=tr[rc].p;
}
void fz(int x)//翻转
{
tr[x].v=0;swap(lc,rc);
tr[lc].v^=1;tr[rc].v^=1;
}
bool rt(int x){return tr[tr[x].f].son[0]!=x&&tr[tr[x].f].son[1]!=x;}//是否为splay的根
void wh(int x)
{
if(!rt(x))wh(tr[x].f);
if(tr[x].v)fz(x);
}
void rotate(int x,int w)
{
int f=tr[x].f,ff=tr[f].f,r,R;
r=tr[x].son[w];R=f;
tr[R].son[1-w]=r;
if(r)tr[r].f=R;
r=x;R=ff;
if(tr[R].son[0]==f)tr[R].son[0]=r;
else if(tr[R].son[1]==f)tr[R].son[1]=r;
tr[r].f=R;
r=f;R=x;
tr[R].son[w]=r;
tr[r].f=R;
update(f);
update(x);
}
void splay(int x)
{
wh(x);
while(!rt(x))
{
int f=tr[x].f;
if(rt(f))rotate(x,tr[f].son[0]==x);
else
{
int ff=tr[f].f,a=(tr[f].son[0]==x),b=(tr[ff].son[0]==f);
if(a^b)rotate(x,a),rotate(x,b);
else rotate(f,a),rotate(x,a);
}
}
}
void access(int x){for(int y=0;x;x=tr[y=x].f)splay(x),rc=y,update(x);}
void makeroot(int x){access(x);splay(x);tr[x].v^=1;}
int find_root(int x)
{
access(x);splay(x);
while(lc)x=lc;
return x;
}
void split(int x,int y){makeroot(y);access(x);splay(x);}
void link(int x,int y){makeroot(x);tr[x].f=y;access(x);}
void cut(int x,int y){split(x,y);rc=tr[y].f=0;update(x);}
int query(int x,int y){split(x,y);return tr[x].p;}
int fa[N],s[N];//s为子树大小
int findfa(int x){return fa[x]==x?x:fa[x]=findfa(fa[x]);}
void merge(int x,int y)
{
x=findfa(x);y=findfa(y);
if(s[x]<s[y]){fa[x]=y;s[y]+=s[x];}
else {fa[y]=x;s[x]+=s[y];}
}
int main()
{
int n,m,x,y,z,b;
qr(n);qr(m);
for(int i=1;i<=n;i++)fa[i]=i,s[i]=1;
for(int i=1;i<=m;i++)
qr(x),qr(y),qr(z),qr(b),a[i]=(edge){x,y,z,b};
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++)d[n+i]=a[i].a;
//因为这是一棵无根树,所以不能把边权压到子节点,我们需要多定义m个点,把边权压到这些点上,其他点的权为0就行
int ans=N;
for(int i=1;i<=m;i++)
{
x=a[i].x;y=a[i].y;bool v=1;
if(findfa(x)==findfa(y))//再加边就会形成回路。如果最大权的边较大,就换下
{
z=query(x,y);
if(d[z]>a[i].a)cut(a[z-n].x,z),cut(z,a[z-n].y);
else v=0;
}
else merge(x,y);
if(v)link(a[i].x,i+n),link(i+n,a[i].y);
if(findfa(1)==findfa(n))
ans=min(ans,a[i].b+d[query(1,n)]);
}
if(ans==N)puts("-1");
else printf("%d\n",ans);
return 0;
}