P1993 小 K 的农场(差分约束)(内附封面)

小 K 的农场

题目描述

小 K 在 MC 里面建立很多很多的农场,总共 n n n 个,以至于他自己都忘记了每个农场中种植作物的具体数量了,他只记得一些含糊的信息(共 m m m 个),以下列三种形式描述:

  • 农场 a a a 比农场 b b b 至少多种植了 c c c 个单位的作物;
  • 农场 a a a 比农场 b b b 至多多种植了 c c c 个单位的作物;
  • 农场 a a a 与农场 b b b 种植的作物数一样多。

但是,由于小 K 的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合。

输入格式

第一行包括两个整数 n n n m m m,分别表示农场数目和小 K 记忆中的信息数目。

接下来 m m m 行:

  • 如果每行的第一个数是 1 1 1,接下来有三个整数 a , b , c a,b,c a,b,c,表示农场 a a a 比农场 b b b 至少多种植了 c c c 个单位的作物;
  • 如果每行的第一个数是 2 2 2,接下来有三个整数 a , b , c a,b,c a,b,c,表示农场 a a a 比农场 b b b 至多多种植了 c c c 个单位的作物;
  • 如果每行的第一个数是 3 3 3,接下来有两个整数 a , b a,b a,b,表示农场 a a a 种植的的数量和 b b b 一样多。

输出格式

如果存在某种情况与小 K 的记忆吻合,输出 Yes,否则输出 No

样例 #1

样例输入 #1

3 3
3 1 2
1 1 3 1
2 2 3 2

样例输出 #1

Yes

提示

对于 100 % 100\% 100% 的数据,保证 1 ≤ n , m , a , b , c ≤ 5 × 1 0 3 1 \le n,m,a,b,c \le 5 \times 10^3 1n,m,a,b,c5×103

前置芝士:差分约束

大致思路

整理题意后,可得 m 条信息有如下三种形式:

a i − a j ≥ c a_{i}-a_{j}\ge c aiajc

a i − a j ≤ c a_{i}-a_{j}\le c aiajc

a i = a j a_{i}=a_{j} ai=aj

可以将式子转化为下面的形式:

a j ≤ a i − c a_{j}\le a_{i}-c ajaic

a i ≤ a j + c a_{i}\le a_{j}+c aiaj+c

a i ≤ a j + 0 a_{i}\le a_{j}+0 aiaj+0 a j ≤ a i + 0 a_{j}\le a_{i}+0 ajai+0a

在 SPFA 中如下方式更新 dis 数组。

for(int i=0;i<ve[x].size();i++){
	int xv=ve[x][i].v,xw=ve[x][i].w;
	if(dis[xv]>dis[x]+xw){
		dis[xv]=dis[x]+xw;
		if(vis[xv]==0){
			q.push(xv);
			vis[xv]=1;
		}
	}
}

也就是 d i s i = min ⁡ { d i s j + < j , i > } dis_{i}=\min\left\{dis_{j}+<j,i>\right\} disi=min{disj+<j,i>}

于是在遇到 a i ≤ a j + c a_{i}\le a_{j}+c aiaj+c

这样的不等式时,我们可以从 j 到 i 建一条边权为 b 的 有向边。

为了避免图不连通的情况,我们需要一个超级源点 n+1,与点 i 之间连一条边权为 0 的边。

那么怎么判断有没有解呢?

那就是判断 负环。

那么又怎么判断负环呢?

只要用一个数组来统计每个点的入队次数,如果某个点的入队次数 ≥ n + 1 \ge n+1 n+1 则说明无解,输出 No,否则输出 Yes

AC CODE

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+234;
#define int long long int
int n,m,ans=0,cnt=0;
int dis[N],cont[N];
bool vis[N];
struct node{
	int v,w;	
};
vector<node> ve[N];
void SPFA(){
	memset(dis,0x7f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(cont,0,sizeof(cont));
	queue<int> q;
	dis[n+1]=0;
	vis[n+1]=1;
	cont[n+1]++;
	q.push(n+1);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=0;i<ve[x].size();i++){
			int xv=ve[x][i].v,xw=ve[x][i].w;
			if(dis[xv]>dis[x]+xw){
				dis[xv]=dis[x]+xw;
				if(vis[xv]==0){
					q.push(xv);
					cont[xv]++;
					vis[xv]=1;
					if(cont[xv]>n+1){
						cout<<"No"<<endl;
						return;
					}
				}
			}
		}
	}
	cout<<"Yes"<<endl;
	return;
}
signed main(){
	node tmp;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int op,a,b,c;
		//cnt++;
		cin>>op>>a>>b;
		if(op==1){//b-a<=-c
			cin>>c;
			tmp.v=b,tmp.w=-c;
			ve[a].push_back(tmp);
		}
		else if(op==2){//a-b<=c
			cin>>c;
			tmp.v=a,tmp.w=c;
			ve[b].push_back(tmp);
		}
		else if(op==3){//a-b<=0&&a-b>=0
			tmp.v=a;tmp.w=0;
			ve[b].push_back(tmp);
			tmp.v=b;
			ve[a].push_back(tmp);
		}
	}
	for(int i=1;i<=n;i++){
		tmp.v=i;tmp.w=0;
		ve[n+1].push_back(tmp);
	}
	SPFA();
	return 0;
}

附封面

请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值