【差分约束】Cashier Employment

 

 

题目描述

            德黑兰的一家每天24小时营业的超市,需要一批出纳员来满足它的需求。超市经理雇佣你来帮他解决一个问题————超市在每天的不同时段需要不同数目的出纳员(例如,午夜只需一小批,而下午则需要很多)来为顾客提供优质服务,他希望雇佣最少数目的纳员。
            超市经历已经提供一天里每一小时需要出纳员的最少数量————R(0),R(1),...,R(23)。R(0)表示从午夜到凌晨1:00所需要出纳员的最少数目;R(1)表示凌晨1:00到2:00之间需要的;等等。每一天,这些数据都是相同的。有N人申请这项工作,每个申请者i在每天24小时当中,从一个特定的时刻开始连续工作恰好8小时。定义ti(0<=ti<=23)为上面提到的开始时刻,也就是说,如果第i个申请者被录用,他(或她)将从ti时刻开始连续工作8小时。
            试着编写一个程序,输入R(i),i=0,...,23,以及ti,i=1,...,N,它们都是非负整数,计算为满足上述限制需要雇佣的最少出纳员数目、在每一时刻可以有比对应R(i)更多的出纳员在工作

输入

            输入文件的第1行为一个整数T,表示输入文件中测试数据的数目(至多20个)。每个测试数据第一行为24个整数,表示R(0),R(1),...,R(23),R(i)最大可以取到1000。接下来一行是一个整数N,表示申请者的数目,0<=N<=1000。接下来有N行,每行为一个整数ti,0<=ti<=23,测试数据之间没有空行。

输出

           对输入文件中的每个测试数据,输出占一行,为需要雇佣的出纳员的最少数目。如果某个测试数据没有解。则输出"No Solution"。

样例输入

1
1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
5
0
23
22
1
10

样例输出

1

提示

关键点依然在于怎么建边,首先数组dist[i]表示前i(包括i)个小时雇佣的总人数,然后题目中的时间是0-23点,为了方便程序书写把所有时间点加1变成1-24点,题目中的need[i]数组表示第i点需要的最少人数,work[i]表示i点进入工作的人数。

对于每一个i点,新加入的人数可以为work[i]也可以不加,所以我们建两条边i-1->i点权值为0的边,i->i-1权值为-work[i]的边。

如果下标i>8,那么i时工作的人数=dist[i]-dist[i-8],就得到了式子dist[i]-dist[i-8]>=need[i],所以我们要建i-8 -> i权值为need[i]的边;如果下标i<8,那么i时工作的人数=dist[i]+dist[24]-dist[i+24-8],得到式子dist[i]+dist[24]-dist[i+16]>=need[i],而dist[24]也就是要求的最少人数sum,所以dist[i]-dist[i+16]>=need[i]+sum,建一条i+16->i权值为need[i]-sum的边。最后再建一条从0到24的权值为sum的边。

然后从0开始枚举需要的人数进入spfa即可。

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define sc(x){scanf("%d",&x);}
#define INF 0x7f7f7f7f
int n,tot=0,need[25],work[25],head[25],dist[25],vis[25],used[25];
struct Edge
{
	int to,next,weight;
}edge[200000];
void add_edge(int from,int to,int weight)
{
	edge[++tot].to=to;
	edge[tot].weight=weight;
	edge[tot].next=head[from];
	head[from]=tot;
}
int spfa(int sum)
{
	for(int i=0;i<=24;i++)
	{
		dist[i]=-INF;
		vis[i]=0;
		used[i]=0;
	}
	dist[0]=0;
	vis[0]=1;
	used[0]=1;
	queue<int>q;
	q.push(0);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i!=-1;i=edge[i].next)
		{
			int v=edge[i].to,w=edge[i].weight;
			if(dist[v]<dist[u]+w)
			{
				dist[v]=dist[u]+w;
				if(!vis[v])
				{
					vis[v]=1;
					used[v]++;
					if(used[v]>24)
						return 0;
					q.push(v);
				}
			}
		}
	}
	return dist[24]==sum;
}
int main()
{
	int t;
	sc(t);
	while(t--)
	{
		bool flag=false;
		memset(need,0,sizeof(need));
		memset(work,0,sizeof(work));
		for(int i=1;i<25;i++)
			sc(need[i]);
		sc(n);
		for(int i=1;i<=n;i++)
		{
			int x;
			sc(x);
			work[x+1]++;//由于题目是从0-23,我们在程序中1-24好写,所以我们需要把每个数加1
		}
		for(int k=0;k<=n;k++)//枚举需要的人数
		{
			tot=0;
			memset(head,-1,sizeof(head));
			for(int i=1;i<25;i++)
			{
				add_edge(i-1,i,0);
				add_edge(i,i-1,-work[i]);
				if(i>=8)
					add_edge(i-8,i,need[i]);
				else
					add_edge(16+i,i,need[i]-k);
			}
			add_edge(0,24,k);//建一条从0->24的边,权值为k,表示前24小时内总雇佣的人数
			if(spfa(k))
			{
				flag=true;
				cout<<k<<endl;
				break;
			}
		}
		if(flag==false)
			cout<<"No Solution"<<endl;
	}
	return 0;
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值