【差分约束】糖果

题目描述

幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,lxhgww需要满足小朋友们的K个要求。幼儿园的糖果总是有限的,lxhgww想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

输入

输入的第一行是两个整数N,K。

接下来K行,表示这些点需要满足的关系,每行3个数字,X,A,B。

如果X=1, 表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多;

如果X=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果;

如果X=3, 表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果;

如果X=4, 表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果;

如果X=5, 表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果;

输出

输出一行,表示lxhgww老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出-1。

样例输入

5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1

样例输出

11

题解

今天学的是差分约束的问题,听了同学说差分是需要最短路径之后我就回去看了最短路径,以前只会Dijkstra算法,前几天学了链式前向星之后就自然而然去看了spfa算法,然后等spfa掌握之后差分约束的影子也就差不多出来了。我的理解是差分约束就是根据题目所给的约束条件来建有向边,然后题目如果求最大值就求单源最短路径,如果求最小值就求最长路径,具体怎么建立边还要跟据题目要求。

case1的情况就是a=b,用管理运筹学的方法把它拆分为a>=b和a<=b,我们就在b->a和a->b之间各建一条权值为0的有向边;

case2的情况就是a<b,那么我们就在建一条a->b的有向边,由于a必须小于b,而题目要求的是最少需要的糖果,那么b至少要比a大1,所以这条边权值为1,然后如果当输入的a=b时,就与a<b冲突,此时整个方程无解;

case3的情况是a>=b,我们建一条b->a的有向边,由于a可以等于b,所以权值为0;

case4的情况是a>b,同理建一条b->a权值为1的有向边,且a=b时整道题无解;

case5的情况是a<=b,同理建一条a->b权值为0的有向边。

然后我们设一个0点,给0到每一个小朋友赋一条权值为 1的有向边,保证每个小朋友最少有一颗糖,然后就像单源最短路径一样进入spfa函数,由于题目求的是最小值,所以我们要跑从0点到每一个点的最长路径:在dist[v]<dist[u]+w的时候更新dist值。最终得到的dist数组的值就是每个小朋友最少需要的糖果数目,求和之后就是最终需要的糖果总数。

#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
#define ll long long
const int maxn=2e5+10;
int n,m,tot=0,head[maxn],dist[maxn],vis[maxn],in[maxn];
ll ans=0;
struct Edge
{
	int to,weight,next;
}edge[maxn*2];
void add_edge(int from,int to,int w)
{
	edge[++tot].to=to;
	edge[tot].next=head[from];
	edge[tot].weight=w;
	head[from]=tot;
}
bool spfa()
{
	queue<int>q;
	q.push(0);
	vis[0]=1;
	dist[0]=0;
	in[0]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to,w=edge[i].weight;
			if(dist[v]<dist[u]+w)//求最小值跑最长路径
			{
				dist[v]=dist[u]+w;
				in[v]++;
				if(in[v]>=n)
					return 0;
				if(!vis[v])//如果v没有入队
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	return 1;
}
int main()
{
	scanf("%d%d",&n,&m);
	while(m--)
	{
		int a,b,x;
		scanf("%d%d%d",&x,&a,&b);
		switch(x)
		{
			//a=b转换为a<=b;a>=b即a->b、b->a至少需要0
			case 1:
				add_edge(a,b,0);
				add_edge(b,a,0);
				break;
			//a<b即a->b至少需要1
			case 2:
				if(a==b)//如果a=b则与a->b至少需要1矛盾,无解
				{
					printf("-1");
					return 0;
				}
				add_edge(a,b,1);
				break;
			//a>=b即b->a至少需要0
			case 3:
				add_edge(b,a,0);
				break;
			//a>b即b->a至少需要1
			case 4:
				if(a==b)//如果a=b则与b->a至少需要1矛盾,无解
				{
					printf("-1");
					return 0;
				}
				add_edge(b,a,1);
				break;
			//a<=b即a->b至少需要0
			case 5:
				add_edge(a,b,0);
				break;
		}
	}
	for(int i=n;i>0;i--)
		add_edge(0,i,1);//每个小朋友至少一颗
	if(!spfa())
	{
		printf("-1");
		return 0;
	}
	for(int i=1;i<=n;i++)
		ans+=dist[i];
	printf("%lld",ans);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值