SPFA
题目要求求最小值。
建原点
0
\mathfrak{0}
0,也就是要
∑
d
i
s
[
x
]
−
d
i
s
[
0
]
\mathfrak{\sum{dis[x]-dis[0]}}
∑dis[x]−dis[0] 最小。
最小值受到
d
i
s
[
x
]
−
d
i
s
[
0
]
≥
v
a
l
[
x
]
[
0
]
\mathfrak{dis[x]-dis[0]\ge val[x][0]}
dis[x]−dis[0]≥val[x][0]的约束也即
d
i
s
[
0
]
+
v
a
l
[
x
]
[
0
]
≤
d
i
s
[
x
]
\mathfrak{dis[0]+val[x][0]\le dis[x]}
dis[0]+val[x][0]≤dis[x]
连边跑差分约束,最长路。
然后最长路就
d
i
j
k
s
t
r
a
\mathfrak{dijkstra}
dijkstra用不了了,不过数据范围也不允许
B
e
l
l
m
a
n
−
F
o
r
d
\mathfrak{Bellman-Ford}
Bellman−Ford
总之就用
s
p
f
a
\mathfrak{spfa}
spfa期望过一个吧(
一定有约束,所以不用判连通。不过要判负环(输出 " − 1 " \mathfrak{"-1"} "−1"的情况)
听说这题甚至构造数据试图卡
s
p
f
a
\mathfrak{spfa}
spfa?有点恐怖的,难道还能
d
i
j
k
s
t
r
a
\mathfrak{dijkstra}
dijkstra
(不过好像也可以缩点+拓扑排序的方式来做)
总之
s
l
f
+
l
f
u
\mathfrak{slf}+\mathfrak{lfu}
slf+lfu大法好,不过要是刻意构造数据就死了【参考】
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
using namespace std;
#define getchar() (frS==frT&&(frT=(frS=frBB)+fread(frBB,1,1<<12,stdin),frS==frT)?EOF:*frS++)
#define ll long long
char frBB[1<<12],*frS=frBB,*frT=frBB;
inline void read(int&T)
{
int x=0;char ch=getchar();bool w=0;
while(!isdigit(ch))w|=(ch=='-'),ch=getchar();
while(isdigit(ch))x=x*10+(ch-48),ch=getchar();
T=w?-x:x;
}
inline void write(ll T)
{
if(T<0)putchar('-'),T=-T;
if(T>9)write(T/10);
putchar(T%10+48);
}
#define add_edge(a,b,c) nxt[++tot]=head[a],head[a]=tot,to[tot]=b,val[tot]=c,slf-=c
int limR,limL,slf;
int N,K,tot=0;
ll ans=0;
int nxt[300005]={},to[300005]={},head[100005]={},val[300005]={};
int dis[100005]={},ext[100005]={},vt[100005]={};
bool vis[100005]={};
deque<int>Q;
bool spfa()
{
memset(dis,0x3f,sizeof(dis));
Q.push_back(0); dis[0]=0; ext[0]=0; vt[0]=1;
int cnt=0;
while(!Q.empty())
{
int x=Q.front(); vis[x]=0; Q.pop_front(); ++cnt;
for(int i=head[x];i;i=nxt[i])
{
if(dis[to[i]]>dis[x]+val[i])
{
dis[to[i]]=dis[x]+val[i];
if(!vis[to[i]])
{
vis[to[i]]=1; ++vt[to[i]];
if((((!Q.empty())&&dis[to[i]]>dis[Q.front()]+slf))||(vt[to[i]]<=limR&&vt[to[i]]>=limL))Q.push_back(to[i]);
else Q.push_front(to[i]);
ext[to[i]]=ext[x]+1;
if(ext[to[i]]>N)return 1;
}
}
}
}
return 0;
}
int main()
{
limL=2; limR=sqrt(N);
read(N); read(K);
for(int X,A,B,i=1;i<=K;++i)
{
read(X); read(A); read(B);
switch(X)
{
case 1:
add_edge(A,B,0),add_edge(B,A,0);
break;
case 2:
if(A==B)
{
printf("-1");
return 0;
}
add_edge(A,B,-1);
break;
case 3:
add_edge(B,A,0);
break;
case 4:
if(A==B)
{
printf("-1");
return 0;
}
add_edge(B,A,-1);
break;
case 5:
add_edge(A,B,0);
break;
}
}
for(int i=1;i<=N;++i)add_edge(0,i,-1);
slf=sqrt(slf);
if(spfa())
{
puts("-1");
return 0;
}
for(int i=1;i<=N;++i)ans+=dis[i];
write(-ans);
return 0;
}
缩点+拓扑排序
s p f a \mathfrak{spfa} spfa的复杂度上界是 Θ ( N M ) \mathcal{\Theta(NM)} Θ(NM),所以 s p f a \mathfrak{spfa} spfa跑差分约束的做法其实不是正解。
图中只有五种约束关系。
如果关系不成环,那就很好做了;如果关系成环呢?
1
\mathfrak{1}
1代表两个点一样,可以缩成一个。
关系
2
,
4
\mathfrak{2,4}
2,4是差不多的,如果靠这两种关系成环,环里面的点值都相同,也可以缩点。
而
3
,
5
\mathfrak{3,5}
3,5成环无解。
所以先缩点,然后用剩下的
2
,
4
\mathfrak{2,4}
2,4和
3
,
5
\mathfrak{3,5}
3,5分别统计。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<cctype>
#include<cmath>
#include<ctime>
#include<queue>
using namespace std;
#define getchar() (frS==frT&&(frT=(frS=frBB)+fread(frBB,1,1<<12,stdin),frS==frT)?EOF:*frS++)
#define ll long long
char frBB[1<<12],*frS=frBB,*frT=frBB;
inline void read(int&T)
{
int x=0;char ch=getchar();bool w=0;
while(!isdigit(ch))w|=(ch=='-'),ch=getchar();
while(isdigit(ch))x=x*10+(ch-48),ch=getchar();
T=w?-x:x;
}
inline void write(ll T)
{
if(T<0)putchar('-'),T=-T;
if(T>9)write(T/10);
putchar(T%10+48);
}
#define add_edge(a,b) nxt[++tot]=head[a],head[a]=tot,to[tot]=b
#define readd_edge(a,b,c) bnxt[++tot]=bhead[a],bhead[a]=tot,bto[tot]=b,type[tot]=c,++in[b]
int N,K,tot=0;
long long ans=0;
int head[100005]={},to[200005]={},nxt[200005]={},dfn[100005]={},low[100005]={};
int dfs_id=0;
int stk[100005]={},col[100005]={},siz[100005]={};
int bhead[100005]={},bto[200005]={},bnxt[200005]={};
bool type[200005]={};
struct adt
{
int x,u,v;
adt(int a=0,int b=0,int c=0)
{
x=a; u=b; v=c;
}
}E[200005];
void tarjan(int x)
{
dfn[x]=low[x]=++dfs_id; stk[++stk[0]]=x;
for(int i=head[x];i;i=nxt[i])
{
if(!dfn[to[i]])
{
tarjan(to[i]);
low[x]=min(low[x],low[to[i]]);
}
else if(!col[to[i]])
{
low[x]=min(low[x],dfn[to[i]]);
}
}
if(low[x]==dfn[x])
{
++col[0];
while(stk[0])
{
col[stk[stk[0]]]=col[0];
++siz[col[0]];
if(stk[stk[0]--]==x)break;
}
}
}
queue<int>Q;
int in[100005]={};
int an[100005]={};
void topo_sort()
{
for(int i=1;i<=col[0];++i)if(!in[i])Q.push(i),an[i]=1;
while(!Q.empty())
{
int x=Q.front(); Q.pop();
for(int i=bhead[x];i;i=bnxt[i])
{
--in[bto[i]];
if(!in[bto[i]])Q.push(bto[i]);
an[bto[i]]=max(an[bto[i]],an[x]+type[i]);
}
}
for(int i=1;i<=col[0];++i) if(in[i]) { printf("-1"); exit(0);}
}
int main()
{
scanf("%d%d",&N,&K);
for(int x,u,v,i=1;i<=K;++i)
{
scanf("%d%d%d",&E[i].x,&E[i].u,&E[i].v);
switch(E[i].x)
{
case 1:
add_edge(E[i].u,E[i].v);
add_edge(E[i].v,E[i].u);
break;
case 3:
add_edge(E[i].v,E[i].u);
break;
case 5:
add_edge(E[i].u,E[i].v);
break;
default:break;
}
}
for(int i=1;i<=N;++i)if(!dfn[i])tarjan(i);
tot=0;
for(int i=1;i<=N;++i)
{
for(int j=head[i];j;j=nxt[j])
{
if(col[i]!=col[to[j]])readd_edge(col[i],col[to[j]],0);
}
}
for(int i=1;i<=K;++i)
{
if(E[i].x==2)
{
if(col[E[i].u]==col[E[i].v])
{
printf("-1");
return 0;
}
readd_edge(col[E[i].u],col[E[i].v],1);
}
if(E[i].x==4)
{
if(col[E[i].u]==col[E[i].v])
{
printf("-1");
return 0;
}
readd_edge(col[E[i].v],col[E[i].u],1);
}
}
topo_sort();
for(int i=1;i<=col[0];++i)ans+=1ll*an[i]*siz[i];
printf("%lld",ans);
return 0;
}
Summary
差分约束问题有时候可以用拓扑排序求解
(不考虑缩点之类的追加部分的话这好像有专门的叫法,叫做求关键路径)
用拓扑排序的话,相对于最短路可以降低复杂度。
实际上差分约束更多是一种解决问题的思考方法这样的地位