题意:小K总共有(n(n<=50000))个农场,以至于他自己都忘记了每个农场中种植作物的具体数量了,他只记得一些含糊的信息(共m个),以下列三种形式描述:
- 农场a比农场b至少多种植了c个单位的作物,
- 农场a比农场b至多多种植了c个单位的作物,
- 农场a与农场b种植的作物数一样多。
但是,由于小K的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合.
三种形式其中第一种形如(X_a-X_b>=c),第二种形如(X_a-X_b<=c),第三种是等式形如(X_a-X_b=0).题目给定n个这样的不等式或者等式,很容易想到要用差分约束来做.
对于第一种等式,我们将其转化成(X_b-X_a<=-c),然后连一条a到b的权值为-c的有向边,对于第二种等式,不用转化,直接连一条从b到a的权值为c的有向边,对于第三种等式,连一条权值为0的a,b之间的无向边.
然后差分约束的套路都是,新建立一个节点0,然后分别向其余n个点连一条权值为0的有向边.
建图之后,从0开始跑spfa求最短路,同时判断负环.众所周知,spfa判断负环有各种玩法.下面第一份代码是中规中矩的spfa判负环,需要吸口氧才能过.第二份代码是铤而走险的做法,具体见代码注释.
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=50005;
int n,m,dis[N],visit[N],cnt[N],sum[N];
int tot,head[N],to[N],nxt[N],w[N];
inline void add(int a,int b,int c){
nxt[++tot]=head[a];head[a]=tot;
to[tot]=b;w[tot]=c;
}
queue<int>q;
inline int spfa(){
memset(dis,0x3f,sizeof(dis));
dis[0]=0;visit[0]=1;q.push(0);++sum[0];
while(q.size()){
int x=q.front();q.pop();visit[x]=0;
for(int i=head[x];i!=-1;i=nxt[i]){
int y=to[i],z=w[i];
if(dis[y]>dis[x]+z){
dis[y]=dis[x]+z;
cnt[y]=cnt[x]+1;if(cnt[y]>=n)return 0;
//cnt[y]表示从0到y的最短路径包含的边数,若大于等于n,则判断有负环
if(!visit[y]){
++sum[y];if(sum[y]>=n)return 0;
//sum[y]表示点y入队的次数,若大于等于n,也判断有负环,这个超级低效
q.push(y);visit[y]=1;
}
}
}
}
return 1;
}
int main(){
n=read();m=read();memset(head,-1,sizeof(head));
for(int i=1;i<=m;++i){
int opt=read(),a=read(),b=read();
if(opt==1){int val=read();add(a,b,-val);}
if(opt==2){int val=read();add(b,a,val);}
if(opt==3){add(a,b,0);add(b,a,0);}
}
for(int i=1;i<=n;++i)add(0,i,0);
if(spfa())puts("Yes");
else puts("No");
return 0;
}
因为只有判断负环的一出代码优化了,所以只放spfa的代码,其余都没变动.
queue<int>q;
inline int spfa(){
memset(dis,0x3f,sizeof(dis));
dis[0]=0;visit[0]=1;q.push(0);++sum;
while(q.size()){
int x=q.front();q.pop();visit[x]=0;
for(int i=head[x];i!=-1;i=nxt[i]){
int y=to[i],z=w[i];
if(dis[y]>dis[x]+z){
dis[y]=dis[x]+z;
cnt[y]=cnt[x]+1;if(cnt[y]>=n)return 0;//同上
if(!visit[y]){
++sum;if(sum>=N)return 0;
//sum表示所有点入队的总次数,我就随便搞了一个稍微大一点的数来判断,跑得飞快
//然后这个判断是有一定风险型的,有可能出现错误判断.
q.push(y);visit[y]=1;
}
}
}
}
return 1;
}