1 条题解
-
0
自动搬运
来自洛谷,原作者为

huanxiong_2022
一只退役的的蒟蒻浣熊搬运于
2025-08-24 21:50:07,当前版本为作者最后更新于2023-09-22 15:47:41,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
P3553 [POI2013] INS-Inspector
摘要:
- 二分 ,每次沿时间轴检验前 个是否冲突。
- 人的记录使他有一个必须存在的时间段。
- 逐时间点、尽可能使人数最少地满足记录要求,并检查是否使用超过 个人。
题意:
公司有 个员工和 个记录,每人只会在连续的一段时间内工作。现在给出 条记录分别是谁写的、什么时候写的以及写的时候除了他还有多少人。求最大的 使得前 条记录互不矛盾。
分析:
-
发现 似乎无法直接求得,考虑转化为检验前 条是否合法。
-
发现对于第 条记录,直接检验其与前 条是否冲突似乎不好实现,考虑每次检验 地遍历全部时间点,因此需要二分 。
-
如何检验
-
由于每个员工工作时间连续,所以将记录整合可以得到每个人必须要工作的区间,后文简称“必要区间”,整合区间可以得到每个时间点至少有多少人,检验记录是否小于最小值。以及是否存在对于同一时刻有不同记录。
-
通过了前一个检测的所有情况中,每个时间点的记录人数大于等于实际人数,因此若不限制人数一定合法(即每个人只工作于一个时间点)。问题转化为尝试使用不多于 个人构造一个合法解。
-
沿时间轴检验并时刻判断即可,详见下文。个人认为本题难点是检测的思路和具体实现。
-
-
检验的实现
-
思路:
尝试用最优方式满足限制,并记录使用了多少人,看是否超过 。
-
预处理:
每个时间点 的记录人数、有多少人的“必要区间”以当前时间开始或结束;即 。
-
实现方案不唯一,部分名称含义如下:
:当前至少要有多少人(由且仅由 决定)。
:已经使用过多少人。
:有多少人已经完成了必要区间,但是被迫延后下班。
:有多少人还没到必要区间,但是被迫提前上班。
:没有写记录的人,他们可以自由安排。
:已经完成了工作,离开公司的人,后续安排与他们无关。
注意,初始时认为上述全部为零;另外,上述名称在表述时逻辑上也视作集合。
-
每一步的处理方法
处理顺序:。通过变量上的加减、逻辑上的人员调动(即从某一集合移动到另一集合),用 拼凑出
-
当前点至少有 个人开始工作,逻辑上,要把 或者 拿出 个转化为 ,优先使用 ,不足则增加 。
-
是当前我们构造的方案的人数,若其小于 ,则需要从 中抽调一些。因为 是被 锁定的,所以把抽调的加在 上,逻辑上为要求这些人提前工作。
-
若 大于 ,则先弹掉 ,仍多则弹掉 。
-
在当前时间,有 个人可以结束工作,让他们进入 等待后续调度。
-
最后,判断 是否超限。把上述步骤循环 次即可。
-
-
实现:
本人采用了 实现,用 判断不合法,仅在每次结尾判断即可,中间不需要判哪些变量是不是负数,个人认为比较好写。
注意,输入的是“除了他还有多少人”。
Code:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10,inf=0x3f3f3f3f; int T,n,m; struct Log{int id,time,cnt;}; struct Staff{int bg,ed;}; struct Time{int cnt,bgcnt,edcnt,vis;}; Log g[maxn]; Staff p[maxn]; Time t[maxn]; bool check(int mid) { for(int i=1;i<=n;i++)p[i]={0,0}; for(int i=1;i<=m;i++)t[i]={0,0,0,0}; for(int i=1;i<=n;i++)p[i]={inf,-inf}; for(int i=1;i<=mid;i++) { //同一时刻的记录发生冲突 if( t[g[i].time].cnt!=0 && t[g[i].time].cnt!=g[i].cnt) { return false; } t[g[i].time].cnt = g[i].cnt; t[g[i].time].vis=1; p[g[i].id].bg=min(p[g[i].id].bg,g[i].time); p[g[i].id].ed=max(p[g[i].id].ed,g[i].time); } for(int i=1;i<=n;i++) { if(p[i].bg!=inf) t[p[i].bg].bgcnt++,t[p[i].ed].edcnt++; } int now=0,used=0,exl=0,exr=0; //“必要区间”决定的人数 //使用过的人数 //可以下班但被迫加班的人数 //暂时不用上班但是被迫提前的人数 //即 exl 位于左侧的可拓展人数,exr右侧可拓展 for(int i=1;i<=m;i++) { if(!t[i].vis)continue; now += t[i].bgcnt; if(now>t[i].cnt) return false; //“必要区间”与记录矛盾 while (t[i].bgcnt--) { if(exr)exr--; else used++; } //需要有一些人开始工作,有 exr 就让他开始,没有就新调人来 //注意这里新增加的 used 是不可以被划分成 exl 的,他们会在结束自己的“必要区间”后加入 exl //可以写成 if else 的判断,但是 while 易于理解且不容易错 if(now+exl+exr<t[i].cnt) { int d = t[i].cnt-now-exl-exr; exr+= d; used+= d; } if(now+exl+exr>t[i].cnt) { int d = now+exl+exr-t[i].cnt; while(d--) { if(exl>0)exl--; else exr--; } } now-=t[i].edcnt; exl+=t[i].edcnt; if(used>n) { return false; } } return true; } int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",&g[i].time,&g[i].id,&g[i].cnt); g[i].cnt++; } int l=1,r=m; while(l<r) { // printf("l=%d,r=%d ",l,r); int mid=(l+r+1)>>1; if(check(mid))l=mid; else r=mid-1; } printf("%d\n",l); } return 0; }upd: 2023.09.22 初稿
- 1
信息
- ID
- 2628
- 时间
- 1500ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者