Solution [HNOI2005]狡猾的商人
题目大意:给定m个约束条件,每个约束条件表示(sum_{s}^{t} = v),判断图是否有解
差分约束
我们现在来分析一下题目:
(sum_{s}^{t} = v),这个式子不好办,让我们非常难受。那么我们有没有办法将这个复杂的和式变换成其它的形式呢?
答案是肯定的。对于这种和式,我们可以考虑采用前缀和的方式来将它变换成其它形式
设(sum(i) = sum_0^ia_i) ((a_i)是题目中给定的盈亏值)
那么那个毒瘤的式子就变成了(sum_{s}^{t} = sum(t) - sum(s - 1) = v)
有没有觉得这个式子很眼熟,尤其是两数之差是不是让你想到了差分约束?
可是这个和裸的差分约束还有一点点区别,我们还不能直接套板子,得继续变形
(sum(t) - sum(s - 1) = v iff egin{cases}sum(t) - sum(s - 1) leq v \ sum(t) - sum(s - 1) geq vend{cases})
既有(leq)又有(geq)不好办?乘个(-1)嘛。
(egin{cases}sum(t) - sum(s - 1) leq v \ sum(t) - sum(s - 1) geq vend{cases} iff egin{cases} sum(t) - sum(s-1) leq v \ sum(s - 1) - sum(t) leq -vend{cases})
然后这就是一个裸的差分约束问题了,直接套板子吧。如果有负环则无解。不过需要注意几点小问题:
-
有负权,不能用超级无敌狂拽酷炫人类目前最快的单源最短路径算法Dijkstra
-
图可能不连通(各位应该想到了)。解决办法很简单。原来图里面点的编号是从(0)到(n)(0这个点来自于前缀和),所以直接加一个虚拟点(n + 1),从(n + 1)到每个点连一条权值为0的边,跑SPFA即可
-
别写挂了(这好像是废话Orz)
最后献上码风丑陋无比的代码
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 1280;
const int maxm = 10240 << 1;
struct Edge{
int from,to,dist;
Edge() = default;
Edge(int a,int b,int c):from(a),to(b),dist(c){}
}Edges[maxm];
int head[maxn],nxt[maxm],tot,n,m;
inline void clear_edge(){
tot = 0;
memset(head,0,sizeof(head));
memset(nxt,0,sizeof(nxt));
}
inline void addedge(int from,int to,int dist){
Edges[++tot] = Edge(from,to,dist);
nxt[tot] = head[from];
head[from] = tot;
}//存图没啥好讲的,clear_edge和addedge望文生义吧
int dist[maxn],cnt[maxn],inq[maxn];
inline bool spfa(int s){//SPFA,用STL望巨佬们见谅。返回true代表差分约束有解,返回false代表有负环,即差分约束无解
queue<int> Q;
memset(cnt,0,sizeof(cnt));
memset(inq,0,sizeof(inq));
memset(dist,0x3f,sizeof(dist));
dist[s] = 0;Q.push(s);inq[s] = true;
while(!Q.empty()){
int u = Q.front();Q.pop();
inq[u] = false;
for(int i = head[u];i;i = nxt[i]){
const Edge &e = Edges[i];
if(dist[e.from] + e.dist < dist[e.to]){
dist[e.to] = dist[e.from] + e.dist;
if(!inq[e.to]){
Q.push(e.to);
inq[e.to] = true;
if(++cnt[e.to] > n)return false;
}
}
}
}
return true;
}
int t;
inline void solve(){//对一组数据求解
scanf("%d %d",&n,&m);
clear_edge();
for(int i = 1;i <= m;i++){
int s,t,v;
scanf("%d %d %d",&s,&t,&v);
addedge(s - 1,t,v);
addedge(t,s - 1,-v);//如何连边上文已经提到了
}
for(int i = 0;i <= n;i++)
addedge(n + 1,i,0);
puts(spfa(n + 1) ? "true" : "false");
}
int main(){
scanf("%d",&t);
while(t--)
solve();
return 0;//收工
}