BZOJ3414: Poi2013 Inspector

111 篇文章 0 订阅
17 篇文章 0 订阅

题目大意:一天公司有n个员工和m个员工记录,每个员工只会在连续的一段时间内工作。现在给出m条记录分别是谁写的、什么时候写的以及写的时候除了他还有多少人。求最大的k使得前k条记录互不矛盾

 

挺神的题...

首先二分答案转成判定性问题,这样记录就没有先后顺序之分了

然后考虑什么情况会出现矛盾:

1.记录本身的矛盾:两条记录是同一时刻,但人数不同

         判断方法:这个是很显然的了,可以直接特判

 

2.记录的记载人产生的约束关系而形成的矛盾:

  比如说一个人在a时刻写过记录,又在b时刻写过记录,这就说明a到b的时刻他都在公司,这时假如中间有个人写除了他以外没人了,那肯定就不对了

  判断方法:我们可以对每个人记录一下最晚开始时间和最早结束时间,当成一条线段,对于每个时间节点,看他被几条线段覆盖,如果线段条数大于当前时间记录的人数,就说明一定无解

 

3.根据记录可以构造出一种方案,但是会超过n个人的限制:

  这个跟上一条的不同点在于,他是可以根据约束条件构造出方案的

  比如1时刻1记录有两个人,2时刻1记录有1个人,3时刻1记录有两个人,显然n=3时是可以的,但n=2就不行

  所以我们只需要算出他的最小符合条件的人数,判断一下是否小于等于n就可以了,那么怎么算呢?

  首先我们按照时间顺序来扫,还是像2一样每个人对应一段区间

  除了当前必须人数(2中的线段条数)now和当前最小符合条件的人数total以外,我们开两个变量来辅助计算

  done表示所对应区间已经过去了,还留下来可以继续工作的人数

  notbegin表示所对应区间还没到,但是提前开始工作的人数

  每到一个新的时刻,我们可以比较一下now+done+notbegin与tot[i](这个点记录的人数)的大小

  如果比tot[i]小,说明人数还不够,则我们需要再让一些人提前开始工作

  如果比tot[i]大,那说明人数多了,我们就优先让对应区间已经过去了的人结束工作,如果还是多,那就让notbegin的人结束工作

 

  这时可能会有一个问题,notbegin的区间还没到怎么可以结束工作呢,这样不是相当于无解了吗?

  我一开始也是这么想的,于是直接return false交了一发,WA了

  原因就是,并不是所有的人都有一个确定的对应区间,有很多人都是没写记录的,那么这些人就可以随时开始随时结束,这里notbegin减下去的是这些人

 

 

具体可以看看代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 100010
#define xrf 707185547
using namespace std;
int n,m;
int t[N],u[N],v[N];
int mn[N],mx[N];
int tot[N];
int st[N],en[N];
bool check(int M)
{
	int i,j;
	for(i=1;i<=n;i++)
	{
		mn[i]=xrf;
		mx[i]=-xrf;
	}
	for(i=1;i<=m;i++)
	tot[i]=st[i]=en[i]=0;
	for(i=1;i<=M;i++)
	{
		mn[u[i]]=min(mn[u[i]],t[i]);
		mx[u[i]]=max(mx[u[i]],t[i]);
		if(tot[t[i]]&&tot[t[i]]!=v[i]+1) return false;
		tot[t[i]]=v[i]+1;
	}
	for(i=1;i<=n;i++)
	if(mx[i]!=-xrf)
	{
		st[mn[i]]++;
		en[mx[i]]++;
	}
	int total=0,now=0;
	int done=0,notbegin=0;
	for(i=1;i<=m;i++)
	if(tot[i])
	{
		now+=st[i];
		if(now>tot[i]) return false;
		if(st[i]<=notbegin) notbegin-=st[i];
		else total+=st[i]-notbegin,notbegin=0;
		if(now+notbegin+done<tot[i]) total+=tot[i]-now-notbegin-done,notbegin=tot[i]-now-done;
		else
		{
			if(now+notbegin>tot[i]) /*return false;*/notbegin=tot[i]-now,done=0;
			else done=tot[i]-now-notbegin;
		}
		now-=en[i];done+=en[i];
	}
	if(total>n) return false;
	return true;
}
void doit()
{
	scanf("%d%d",&n,&m);
	int i,j;
	for(i=1;i<=m;i++)
	scanf("%d%d%d",&t[i],&u[i],&v[i]);
	int l=1,r=m+1,mid;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid)) l=mid+1;
		else r=mid;
	}
	printf("%d\n",l-1);
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	doit();
}


 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值