洛谷P4878 [USACO05DEC]Layout G 题解

P4878 [USACO05DEC]Layout G

题目链接:P4878 [USACO05DEC]Layout G

题意:按编号排了 n n n​​ 只奶牛,有的奶牛间必须相距小于等于一个距离,有的奶牛间必须相距大于等于一个距离,问 1 1 1 n n n 的距离最大值

我们可以发现题目给的数据本质上就是 v − u ≥ d i v-u\ge d_i vudi​​​ 或 v − u ≤ d i v-u \le d_i vudi​​​​​​​

这是什么?差分约束!

差分约束:根据三角不等式 d u + w ( u , v ) ≥ d v d_u + w(u,v) \ge d_v du+w(u,v)dv​ 的构成,我们可以把形如 v − u ≥ d i v-u\ge d_i vudi​ 的不等式转化为

v − d i ≥ u v-d_i\ge u vdiu (可以看作 v v v u u u 连了一条边权为 − d i -d_i di 的有向边),同理 v − u ≤ d i v-u \le d_i vudi 转化为 u + d i ≥ v u+d_i \ge v u+div

这里不过多讲解差分约束了

那么为什么差分约束后求最短路就是最大值呢?

因为求最短路是由无穷大向下不断约束得到的,因此得到的是最大值(同理求最长路就是最小值)

那我们只要按题意建图就行了

要注意的是,每个奶牛 i i i 满足 d i − 1 ≤ d i d_{i-1} \le d_{i} di1di ,其中 d d d​ 表示所在位置,因此相邻的也要建边

为什么要强调这一点呢?

如下图:

在这里插入图片描述

如果没有建边,会发现答案为 5 5 5 (如上图所示)

在这里插入图片描述

如果建了边,答案才正确(该情况无解)

那现在只要从 1 1 1 开始跑SPFA就好了

但是还有问题, 1 1 1​ 并不能保证与所有结点连通,而我们知道差分约束无解的情况就是图中有负环

这个问题好解决,我们先在原图上建一个超级结点 0 0 0 与所有结点相连(边权为 0 0 0 ),在 0 0 0 跑一次SPFA判断即可判断解的情况

说了这么多,是不是有点晕(

理一下思路:

  1. 差分约束建图
  2. 相邻编号建图
  3. 0 0 0 结点判断解的情况
  4. 有解则从 1 1 1 开始跑SPFA

代码如下

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define R register
#define MAXN (int)(1e3+5)
#define MAXM (int)(2e4+5)
#define INF 0x3f3f3f3f3f3f3f3f
template<typename T>inline void read(R T &k)
{
	R char ch=getchar();R T x=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	k=x*f;
}
int n,ml,md,m,d[MAXN],vis[MAXN],cnt[MAXN];
struct Edge
{
	int u,v,w,next;
}e[MAXM<<1];
int head[MAXN],pos=1;
void add(R int u,R int v,R int w)
{
	e[pos]={u,v,w,head[u]};
	head[u]=pos++;
}
bool spfa(R int st)
{
	queue<int> q;q.push(st);
	memset(d,0x3f,sizeof(d));
	vis[st]=1;d[st]=0;cnt[st]=1;
	while(!q.empty())
	{
		R int u=q.front();
		q.pop();vis[u]=0;
		for(R int i=head[u]; i; i=e[i].next)
		{
			R int v=e[i].v,w=e[i].w;
			if(d[v]>d[u]+w)
			{
				d[v]=d[u]+w;
				if(!vis[v])
				{
					if(++cnt[v]>n) 
						return 0; // 有0结点,结点数增加1,判负环略有区别 
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return 1;
}
signed main()
{
	read(n);read(ml);read(md);
	for(R int i=1,u,v,w; i<=ml; i++)
	{
		read(u);read(v);read(w); // d_v-d_u<=w -> d_v<=d_u+w
		add(u,v,w);
	}
	for(R int i=1,u,v,w; i<=md; i++)
	{
		read(u);read(v);read(w); // d_v-d_u>=w -> d_v-w>=d_u
		add(v,u,-w);
	}
	for(R int i=1; i<n; i++)
		add(i+1,i,0); // d_i<=d_{i+1}+0
	for(R int i=1; i<=n; i++)
		add(0,i,0);
	if(!spfa(0))return puts("-1"),0;
	spfa(1);
	if(d[n]==INF)puts("-2");
	else printf("%lld\n",d[n]);
	return 0;
}

转载请说明出处

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值