$\DeclareMathOperator*{\argmax}{arg\,max}$

[题目链接](https://codingcompetitions.withgoogle.com/kickstart/round/0000000000050e01/0000000000069881)

题目大意

一排 $N$ 个座位,从左到右编号 $1$ 到 $N$ 。

有 $Q$ 个预定座位的请求,第 $i$ 个请求想要预定编号从 $L_i$ 到 $R_i$ 的所有座位。

可以按任意顺序处理这 $Q$ 个请求,处理一个请求时,把对应区间中尚未被分配的座位分配给这个请求。

试问每个请求最终订到的座位数量的最小值最大可能是多少?

Limits

  • Time limit: 30 seconds per test set.
  • Memory limit: 1GB.
  • Number of test cases $T = 100$.
  • $1 \le N \le 10^6$.
  • $1 \le L_i \le R_i \le N $.
  • $1 \le Q \le 30000 $.
  • For at least 85 of the test cases, $Q \le 3000$.

分析

Observation 1: 第 $i$ 个被处理的请求所获得的座位数量与处理前 $i - 1$ 个请求的顺序无关。

Observation 2:

设 $A, B$ 是相邻的两个请求。假设若先处理 $A$,则 $A$ 得到 $a$ 个座位;若先处理 $B$,则 $B$ 得到 $b$ 个座位;又设 $A, B$ 两请求共得到 $c$ 个座位,显然 $c$ 跟 $A, B$ 被处理的先后顺序无关,且有 $a + b \ge c$ 。不妨设 $a > b$ 。若先 $A$ 后 $B$ 则两个请求得到的座位数的最小值为 $\min(a, c - a) = c - a$;若先 $B$ 后 $A$,则最小值为 $\min(b, c - b)$ 。注意到 $b \ge c - a$ 且 $ c - b > c - a $,故有 $\min(b, c - b) \ge \min(a, c - a)$;换言之,对于相邻的两个请求,应当先处理分到座位较少的那个请求。

注意:满足上述条件的处理顺序也许不唯一,因为 $a, b$ 的大小关系依赖于之前处理的那些请求。

到这里我就想不通了。


官方题解

We can observe that for a chosen ordering of the requests, the number of seats that the system books in the last request does not depend on the ordering of the previous $Q - 1$ requests. So, we could start by finding the request to be processed last and move backwards towards the earlier requests.

这一段我能理解,但问题在于 how can you determine which request should be processed last in an optimal solution?


我想通了。

考虑若干个请求的任意排列,按此排列进行操作,把每个请求获得的座位数的最小值简称为此「排列的最小值」。

Key observation:

从任意 $k$($k > 1$)个请求的任意排列中拿走一个请求,余下的 $k - 1$ 请求的排列的最小值一定不小于原先 $k$ 个请求的排列的最小值。

定义函数 $f(x)$ 表示最后(即第 $Q$ 个)处理请求 $x$,$x$ 能得到多少个座位。将 $f(x)$ 的最大值记作 $m$ 。

对于 $Q$ 个请求的任一排列 $P := p_1, p_2, \dots, p_Q$,设其最小值为 $a$ 。

假设 $f(p_Q) < m$,我们有 $a \le f(p_Q) < m$,又设 $f(p_i) = m$;

考虑排列 $P' := p_1, p_2, \dots, p_{i -1}, p_{i + 1}, p_{i + 2}, \dots, p_{Q}$ ,设其最小值为 $b$ 。

对比 $P$ 和 $P'$ 这两个排列;显然,$p_1, p_2 \dots, p_{i - 1}$ 这些请求获得的座位数不变;在 $P'$ 中,由于少了 $p_{i}$ 的竞争,$p_{i + 1}, p_{i + 2}, \dots, p_{Q}$ 这些请求获得的座位数不会减少;于是有 $ b \ge a $ 。

考虑排列 $P'' := p_1, p_2, \dots, p_{i -1}, p_{i + 1}, \dots, p_{Q}, p_i$,设其最小值为 $c$,易见 $ c = \min(b, m)$ 。

由于 $b \ge a$ 且 $ m > a$ 故有 $c \ge a$ 。所以把 $\argmax_x f(x)$ 放最后能导致最优解。

官方题解的做法是根据上述贪心策略,用数据结构辅助求解 $\argmax_x f(x)$;这里不详述,只给出代码

接下来给出另一种做法:二分答案。

Another approach:二分答案

这种做法来自 Mahmoudian 的 submission,其思路比官方解法更为简单,巧妙而富有启发性。

此做法的最坏时间复杂度是 $O(Q^2\log N)$;不过由于常数小并且在随机数据上复杂度离此上界甚远,实际上相当快,明显快于官方题解中给出的基于线段树的算法。

我对其代码做了少许修改并加了注释

  1. const int Q = 30000;
  2. int n, q, xl[Q], xr[Q], mvl[Q]; // mvl意思是move l to a new index
  3. pair<int, int> sr[Q]; // sr 代表 sort
  4. int check(int k) {
  5. for (int j = 0; j < q; ++j)
  6. mvl[j] = xl[j];
  7. for (int j = 0; j < q; ++j) {
  8. int r = xr[j];
  9. int st = mvl[j]; // st即start,表示当前能分配给j的那一段的左端点
  10. int allowed_after = r;
  11. int cnt = 0;
  12. // 判断能否在[l,r]中分配k个座位给j
  13. for (int i = j + 1; i < q; ++i) {
  14. // 遍历被j包含的那些区间
  15. if (xl[i] >= r) break;
  16. if (xr[i] <= r) {
  17. // 被j包含的区间一定要在j之前处理,不然这些区间一个座位也得不到
  18. // 换言之,j得不到它所包含的那些区间内的座位
  19. if (xl[i] <= st) {
  20. st = max(st, xr[i]);
  21. } else {
  22. cnt += xl[i] - st;
  23. st = max(st, xr[i]);
  24. if (cnt >= k) {
  25. // j 已经获得k个座位。
  26. // allowed_after 之后的那些座位,留给别的区间。
  27. allowed_after = xl[i] - (cnt - k);
  28. break;
  29. }
  30. }
  31. }
  32. }
  33. if (cnt < k) {
  34. cnt += r - st;
  35. if (cnt < k) return 0;
  36. allowed_after = r - (cnt - k);
  37. }
  38. //排序在j之后,与j有交集但没被j包含的那些区间,
  39. //如果左端点在allowed_after之前,那么这个区间要放在j之后处理,
  40. //否则j获得座位就达不到k个。
  41. for (int i = j + 1; i < q; ++i) {
  42. if (xl[i] >= allowed_after) break;
  43. if (xr[i] > r) {
  44. //这一句的效果就相当于把i放在j之后处理
  45. mvl[i] = max(mvl[i], r);
  46. }
  47. }
  48. }
  49. return 1;
  50. }
  51. int main() {
  52. int tc;
  53. cin >> tc;
  54. for (int tt = 1; tt <= tc; ++tt) {
  55. cin >> n >> q;
  56. for (int j = 0; j < q; ++j) {
  57. // 区间采用左闭右开表示,座位改成从0开始编号
  58. cin >> xl[j] >> xr[j], --xl[j];
  59. sr[j] = {xl[j], -xr[j]};
  60. }
  61. //区间按左端点从小到大排序;左端点相同的,按右端点从大到小排序;
  62. //效果是若区间A包含区间B,则B排在A后面
  63. sort(sr, sr + q);
  64. for (int j = 0; j < q; ++j)
  65. xl[j] = sr[j].first, xr[j] = -sr[j].second;
  66. int bl = 0, br = n;
  67. for (int j = 0; j < q; ++j)
  68. br = min(br, xr[j] - xl[j]);
  69. ++br;
  70. while (bl < br - 1) {
  71. int bm = (bl + br) / 2;
  72. if (check(bm)) {
  73. bl = bm;
  74. } else {
  75. br = bm;
  76. }
  77. }
  78. cout << "Case #" << tt << ": " << bl << '\n';
  79. }
  80. }

References

https://blog.csdn.net/Dylan_Frank/article/details/88985444

https://blog.csdn.net/lfhase/article/details/88823761

Kick Start 2019 Round A Contention的更多相关文章

  1. kick start 2019 round D T3题解

    ---恢复内容开始--- 题目大意:共有N个房子,每个房子都有各自的坐标X[i],占据每个房子需要一定花费C[i].现在需要选择K个房子作为仓库,1个房子作为商店(与题目不同,概念一样),由于仓库到房 ...

  2. kick start 2019 round D T2题解

    题目大意:由N个房子围成一个环,G个人分别顺时针/逆时针在房子上走,一共走M分钟,每分钟结束,每个人顺/逆时针走到相邻的房子.对于每个房子都会记录最后时刻到达的人(可能是一群人).最终输出每个人会被几 ...

  3. Kick Start 2019 Round H. Elevanagram

    设共有 $N = \sum_{i=1}^{9} A_i$ 个数字.先把 $N$ 个数字任意分成两组 $A$ 和 $B$,$A$ 中有 $N_A = \floor{N/2}$ 个数字,$B$ 中有 $N ...

  4. Kick Start 2019 Round A Parcels

    题目大意 $R \times C$ 的网格,格子间的距离取曼哈顿距离.有些格子是邮局.现在可以把至多一个不是邮局的格子变成邮局,问每个格子到最近的邮局的曼哈顿距离的最大值最小是多少. 数据范围 $ 1 ...

  5. Kick Start 2019 Round B Energy Stones

    对我很有启发的一道题. 这道题的解法中最有思维难度的 observation 是 For simplicity, we will assume that we never eat a stone wi ...

  6. 【DP 好题】Kick Start 2019 Round C Catch Some

    题目链接 题目大意 在一条数轴上住着 $N$ 条狗和一个动物研究者 Bundle.Bundle 的坐标是 0,狗的坐标都是正整数,可能有多条狗住在同一个位置.每条狗都有一个颜色.Bundle 需要观测 ...

  7. Kick Start 2019 Round F Teach Me

    题目链接 题目大意 有 $N$ 个人,$S$ 项技能,这些技能用 $1, 2, 3, \dots, S$ 表示 .第 $i$ 个人会 $c_i$ 项技能($ 1 \le c_i \le 5 $).对于 ...

  8. Kick Start 2019 Round D

    X or What? 符号约定: $\xor$ 表示异或. popcount($x$) 表示非负整数 $x$ 的二进制表示里数字 1 出现的次数.例如,$13 = 1101_2$,则 popcount ...

  9. Google Kick Start 2019 C轮 第一题 Wiggle Walk 题解

    Google Kick Start 2019 C轮 第一题 Wiggle Walk 题解 题目地址:https://codingcompetitions.withgoogle.com/kickstar ...

随机推荐

  1. BOM基础笔记

    BOM基础 BOM对浏览器的一些操作 1.打开.关闭窗口 •open –蓝色理想运行代码功能 window.open('http://www.baidu.com/', '_self'); <!d ...

  2. Spring Boot教程(四十一)LDAP来管理用户信息(1)

    LDAP简介 LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务.目录服务是一种特殊的数据库系统,其专门针对读 ...

  3. Linux安装JDK、tomcat

    修改tomcat 相关配置必须重启后才生效 如何启动tomcat 在终端框内切换到tomcat 的bin路径下 启动tomcat:./startup.sh 关闭tomcat:./shutdown.sh ...

  4. __new()__与__init__()

    1. __new__:创建对象时调用,会返回当前对象的一个实例.(默认情况下也就是你在类中没有没有重新这个方法,会默认返回当前类的示例,如果你重写了这个方法,但是在方法中没有返回当前类的示例,那么也就 ...

  5. 优雅的退出asyncio事件循环

    import asyncio import functools import os import signal """ 信号值 符号 行为 2 SIGINT 进程终端,C ...

  6. mysql 数据库备份 -- (定时任务)

    场景: 我们经常需要对数据库备份 方式一:mysql 数据备份方式 在linux 备份方式  通常采用 mysqldump -uroot -ppassword  --database 数据库名 > ...

  7. TCP主动打开 之 第一次握手-发送SYN

    tcp客户端与服务器端建立连接需要经过三次握手过程,本文主要分析客户端主动打开中的第一次握手部分,即客户端发送syn段到服务器端: tcp_v4_connect为发起连接主流程,首先对必要参数进行检查 ...

  8. Ngrinder 源码之Maven 项目

    Ngrinder支持Maven结构的测试脚本.使用ScriptHandlerFactory来个脚本选择处理器handler,目前有JythonScriptHandler, GroovyScriptHa ...

  9. Fastadmin 后台表单,外键关键,步骤

    1.在 public\assets\js\backend 目录中找到对应的js,修改 2.对应控制器中加上index()方法:开启关联查询 重点: protected $relationSearch ...

  10. NullPointerException 没有堆栈

    周五在公司搭好的ELK上查看日志,组长让看看其中NullPointerException出现很多的原因. 通过NullPointerException搜索,点看其中一个查看,发现异常的信息就一行jav ...