洛谷P3553/bzoj3414 [POI2013]Inspector 二分答案+贪心

题目分析

首先二分答案,就可以只判断这几条可不可行了。

根据每个人的称述,我们先可以给这些人确定一个大致的“必须存在的区间”,而那些没有称述的人,根据boshi命名法,称其为“幽灵”。

假设当前检查的时刻 i i i,必须存在的人数为 s i s_i si

nows:由于“必须存在的区间”,而导致当前时刻至少有多少人存在。

people:当前已经被“使用”了的人数。

ghost:当前使用了,且其所在区间还在向右延伸的“幽灵”数。

extend:已经使用了的非幽灵人中,所在区间依然在向右延伸的人数。

那么检查的大致流程如下:

  1. 检查是否有关于同一时刻的矛盾称述,再从左往右检查每个时刻。
  2. 检查“必须存在的区间”左端点为当前时刻的非幽灵。因为幽灵的使用更为自由,所以幽灵更应该保留到万不得已的时候用。ghost中那些存在区间是当前考虑的时间前缀的后缀的幽灵,可以通过当前时刻出现的非幽灵的存在区间向前延伸来代替,ghost减小。
  3. nows>s_i,必不合法。接下来就要让nows+extend+ghost=s_i,若左式小了,就添加一些幽灵(同时被使用的人数增加)。若左式大了,由于ghost继续往后延伸还可能能被代替,所以先让extend中的人停止区间延伸,再让ghost中的停止。
  4. 检查“必须存在的区间”右端点为当前时刻的非幽灵,他们所在区间可以继续往右延伸,加入extend中。
  5. 所有时刻检查完后,检查people是否超过了人数。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005;
int T,n,m;
int record_p[N],record_t[N],record_s[N],L[N],R[N],s[N],appear[N],disappear[N];

int check(int lim) {
	for(RI i=1;i<=m;++i) s[i]=-1,appear[i]=disappear[i]=0;
	for(RI i=1;i<=n;++i) L[i]=m+1,R[i]=0;
	for(RI i=1;i<=lim;++i) {
		int p=record_p[i],t=record_t[i];
		if(s[t]!=-1&&s[t]!=record_s[i]) return 0;
		s[t]=record_s[i],L[p]=min(L[p],t),R[p]=max(R[p],t);
	}
	int nows=0,people=0,ghost=0,extend=0;
	for(RI i=1;i<=n;++i) if(R[i]) ++appear[L[i]],++disappear[R[i]];
	for(RI i=1;i<=m;++i) {
		if(s[i]==-1) continue;
		nows+=appear[i];
		if(ghost<appear[i]) people+=appear[i]-ghost,ghost=0;
		else ghost-=appear[i];
		if(nows>s[i]) return 0;
		if(nows+ghost+extend<s[i])
			people+=s[i]-nows-ghost-extend,ghost+=s[i]-nows-ghost-extend;
		if(nows+ghost+extend>s[i]) {
			int kl=nows+ghost+extend-s[i];
			if(kl<=extend) extend-=kl;
			else ghost-=kl-extend,extend=0;
		}
		nows-=disappear[i],extend+=disappear[i];
	}
	return people<=n;
}

int main()
{
	T=read();
	while(T--) {
		n=read(),m=read();
		for(RI i=1;i<=m;++i)
			record_t[i]=read(),record_p[i]=read(),record_s[i]=read()+1;
		int l=0,r=m,mid,ans=0;
		while(l<=r) {
			mid=(l+r)>>1;
			if(check(mid)) ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值