为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。
标签:SPFA/LCT
Time Limit: 30 Sec Memory Limit: 512 MB
Submit: 3139 Solved: 1984
[Submit][Status][Discuss]
为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小E可以借助它们的力量,达到自己的目的。
只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边Ei包含两个权值Ai与Bi。若身上携带的A型守护精灵个数不少于Ai,且B型守护精灵个数不少于Bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。
第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。
输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。
这题看起来很难,不过有一种很简单的水法(不想看的话可以直接跳到下面粗体字)
我们把所有边按照Ai为第一关键字,Bi为第二关键字排序,每加一条边,就跑一次SPFA(当然,dis数组维护的是最大值),用dis[n]+a[i](i为当前加的边去更新答案)。
这种做法的正确性显而易见,然而复杂度确是O(nmk),TLE!
于是新技能get:动态加边SPFA,就是跑完一遍SPFA后,不把dis数组清空,下次加边只将边所连接的两个点加入队列去更新答案,复杂度O(玄学)。
附上代码(比正解还快!)
/**************************************************************
Problem: 3669
User: P1atform
Language: C++
Result: Accepted
Time:3988 ms
Memory:50512 kb
****************************************************************/
#include
#include
#include
#include
#define N 100050
#define M 200050
#define Q 10000050
#define INF 1000000000
#define add(u,v,vv) to[++top]=head[u],head[u]=top,w[top]=v,val[top]=vv
#define For(x) for(int h=head[x],o=w[h],v=val[h];h;o=w[h=to[h]],v=val[h])
using namespace std;
struct edge
{
int u,v,a,b;
bool operator <(edge y) const {if (a==y.a) return b<y.b;return a<y.a;}
}t[M];
int q[Q],bo[N],ans=INF,d[N],l,r,n,m,top=0,to[M*2],head[M*2],w[M*2],val[M*2],x;
inline void read(int &x)
{
x=0;
char ch=getchar();
int f=1;
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
x=x*f;
}
int main()
{
read(n),read(m);
for (int i=1;i<=m;i++) read(t[i].u),read(t[i].v),read(t[i].a),read(t[i].b);
sort(t+1,t+m+1),memset(d,0x7f,sizeof(d)),memset(bo,0,sizeof(bo)),l=0,r=-1,d[1]=0;
for (int i=1;i<=m;i++)
{
add(t[i].u,t[i].v,t[i].b),add(t[i].v,t[i].u,t[i].b);
if (!bo[t[i].u]) bo[t[i].u]=1,q[++r]=t[i].u;
if (!bo[t[i].v]) bo[t[i].v]=1,q[++r]=t[i].v;
if (t[i].a==t[i-1].a&&t[i].b==t[i-1].b) continue;
while (l<=r)
{
x=q[l++];
For(x) if (d[o]>max(d[x],v))
{
d[o]=max(d[x],v);
if (!bo[o]) bo[o]=1,q[r=(r+1)%Q]=o;
}
bo[x]=0;
}
ans=min(ans,d[n]+t[i].a);
}
if (ans==INF) printf("-1\n");else printf("%d\n",ans);
return 0;
}
基于上述的思路,我们同样可以先把边排序。LCT维护动态的生成树,用w[i]表示当前路径b值最大的边。然后用并查集维护连通性,如果即将加边的两个点已经连接,则判断当前路径上权值最大的边是否比当前边的b值大。最后如果1和n联通更新答案即可。
新技能get:由于LCT只能维护点,这里的处理方法就是把一条边拆成一个点加上两条边即可。
#include
#include
#include
#include
#define N 100050
#define INF 1000000000
using namespace std;
typedef long long ll;
struct edge
{
int u,v;
ll a,b;
bool operator <(edge x) const{if (a==x.a) return b<x.b;return a<x.a;}
}t[N*2];
int val[N*2],fa[N*2],n,m,fx,fy,k;
ll ans=INF;
struct lct
{
int s[N*2][2],f[N*2],lz[N*2],top,q[N*2];
ll w[N*2];
inline bool isroot(int x) {return x!=s[f[x]][0]&&x!=s[f[x]][1];}
inline void pushup(int x)
{
if (!x) return;
w[x]=x;
if (s[x][0]) if (val[w[x]]s[x][0]]]) w[x]=w[s[x][0]];
if (s[x][1]) if (val[w[x]]s[x][1]]]) w[x]=w[s[x][1]];
}
inline void pushdown(int x) {if (x&&lz[x]) swap(s[x][0],s[x][1]),lz[s[x][0]]^=1,lz[s[x][1]]^=1,lz[x]=0;}
inline void rotate(int x,int k)
{
int y=f[x],z=f[y];
s[y][!k]=s[x][k],f[x]=z;
if (s[x][k]) f[s[x][k]]=y;
if (!isroot(y)) s[z][y==s[z][1]]=x;
s[x][k]=y,f[y]=x,pushup(y),pushup(x);
}
inline void splay(int x)
{
top=0,q[++top]=x;
for (int i=x;!isroot(i);i=f[i]) q[++top]=f[i];
for (int i=top;i;i--) pushdown(q[i]);
while (!isroot(x))
{
int y=f[x],z=f[y];
if (isroot(y)) {rotate(x,x==s[y][0]);continue;}
if (y==s[z][0])
{
if (x==s[y][0]) rotate(y,1),rotate(x,1);
else rotate(x,0),rotate(x,1);
}
else
{
if (x==s[y][1]) rotate(y,0),rotate(x,0);
else rotate(x,1),rotate(x,0);
}
}
}
inline void access(int x) {for (int i=0;x;i=x,x=f[x]) splay(x),s[x][1]=i,pushup(x);}
inline void makeroot(int x) {access(x),splay(x),lz[x]^=1;}
inline void split(int x,int y) {makeroot(x),access(y),splay(y);}
inline void link(int x,int y) {makeroot(x),f[x]=y;}
inline void cut(int x,int y) {split(x,y);if (x==s[y][0]) f[x]=0,s[y][0]=0;}
}T;
inline int find(int x) {if (fa[x]==x) return x;else return fa[x]=find(fa[x]);}
template inline void read(_T &x)
{
x=0;
int f=0;
char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=1;ch=getchar();}
while (isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
if (f) x=-x;
}
int main()
{
read(n),read(m);
for (int i=1;i<=m;i++) read(t[i].u),read(t[i].v),read(t[i].a),read(t[i].b);
sort(t+1,t+m+1);
for (int i=1;i<=m;i++) val[n+i]=t[i].b;
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=m;i++)
{
fx=find(t[i].u),fy=find(t[i].v);
if (fx!=fy) T.link(t[i].u,n+i),T.link(n+i,t[i].v),fa[fx]=fy;
else
{
T.split(t[i].u,t[i].v),k=T.w[t[i].v];
if (val[k]>t[i].b) T.cut(t[k-n].u,k),T.cut(k,t[k-n].v),T.link(t[i].u,i+n),T.link(i+n,t[i].v);
}
if (find(1)==find(n)) T.split(1,n),ans=min(ans,t[i].a+val[T.w[n]]);
}
if (ans==INF) ans=-1;
printf("%lld\n",ans);
}