洛谷月赛工作报告整理
RiOI R3(被狠狠滴爆标了)
- D & F 题:
- 出题人:irris
- 工作人员:irris,听取MLE声一片,Querainy(审核员 mrsrz 也提供了做法的加强);251Sec(对初版 D 进行了验题,但现在可能并无贡献了)
- 出题过程:在数学课上草稿本枚举出来的题意。当时随便写了几个数据范围然后捏了一个做法就做出来了,这就是初版的 D。
- 解法知识点及题目亮点:
- 比较 adhoc 的交互题。解法本身只涉及到简单的数学知识,但是根据验题人的表现,这可能并不十分容易想出来。
- 题目亮点在于,(D)因式分解、化乘为加的思想和 \(2^{\log n} = \mathcal O(n)\) 本身是比较有趣和新颖的方向,
把常数从 \(2\) 卡到 \(1\) 这一步也比较有趣;(F)采用不断颠倒符号的方式限定了 \(Q\) 的取值范围,这样就做到了每次二分过程都固定只有常数次操作,总体来看还是比较脱离常规的。
- 可能类似的题目与查重细节:
- 可能与其它交互题的唯一类似点在于二分。
- 没有找到和本题思想类似的二分方式,因为大部分二分题目似乎都没有无法避免的后效性。
- 部分分设计:
- 对于 D 题:
- 有 \(q_i \not\in \{1, -1\}\) 的简单前缀模拟 5 pts。
- \(q_i \neq 1\) 或 \(q_i \neq -1\) 的位置非正解分别放了 10 pts。暂且不知道做法(但是也许存在,E_Space 之前看题的时候提出过一些奇怪的乱搞);期待选手表现。
- 对正解有启发性的 \(k = 2^{n-1}\) 和 \(k = 2^{n-2}\) 分别放了 9, 9+11 pts。
- \(k = 2n\) 的常数较劣做法有额外的 20 pts,但这应当不难优化至正解。
- 对于 F 题:
- 类似地,有一个比较简单的每次乘上全局乘积平方的简单做法 11 pts。
- 远古的一种奇怪构造单项式再二分的做法放了 25 pts,实际上是比较麻烦的。
- 因为不清楚选手的乱搞程度,所以在 \(\log_2 n \leq 10\) 时放了一档渐进给分的部分分。主要要求选手缩减 \(S_{\max}\) 至 \(\leq 6n\) 量级。
- 对于 D 题:
- 可能的非正解及数据强度:
- 所有我们能够预料到的非正解均在上面提及并设置了 Subtask。经过理论估算和实际提交,并不能得到超出预期的分数。
- 正解的 \(k, S_{\max}\) 限制是比较紧的(除了 F 题的 \(k\) 因为是期望 2.5 最劣 3 的问题,所以设置得较松,没有刻意去卡),除非提出了等于或更优于 std 的解法,否则应当不能通过。
- 约 20% 的数据为绝对值全部为 \(1\),也有在上面以较小概率随机生成 \(\pm 2, \pm 3\) 和某几个随机大数的情况,所以应该是相当强了。
HCOI(好像比预估的难。)
- A 题,报名人数:
- idea, data, solution from irris
- 出题过程:
- 呃,当时是在马路上等红灯,然后我在尝试一个一个数每个数字的表示的竖线条数(
- 后来发现人数得很慢啊,那波特不是能数得很快吗!这个数字条数变化有啥规律呢?后来就搞了这题。
- 解法涉及的知识点和题目亮点:
- 普及组 T1/2 难度的观察性质。或者暴力打表应该也能瞪出来。提示也就是个小模拟吧。
- 可能类似的题目与查重细节:
- 如洛谷 P1149 等,我们找到的所有关于 火柴棒拼数 这一方面的题目都是和「等式」,或者「数位」有关的问题,但是没有找到计算这种快速计算区间 popcount 的题目。
- 部分分设计:
- 满分为 \(1 \leq l \leq r \leq 10^{18}\)。
- 一开始只有一档 \(\mathcal O((r - l)\log r)\) 的暴力求出来每个 popcount 的分,\(r \leq 10^6\);
- 后来意识到可能有更普及的小朋友不是很能理解双指针划分区间这种状物,所以加了 \(r \leq 10^3\),可以直接枚举 \(l, r\)。
- 不打算给更暴力的分数了,因为感觉这些似乎都是在刻意加大计算量。当然这题也没有指数级做法啥的。
- 考虑到我在每个包里要塞若干保证正确性的点,开太多包可能会超出 \(30\) 个点的限制。倒是不想加多组数据,因为其实感觉没必要,而且普及组选手也可能可以套数据来获取结论(?但是要是真的一个一个套数据那也挺蠢的。
- 可能的非正解与测试数据强度:
- 只考虑到 \((10k + 2)\) 和 \((10k + 3)\) 一种的;
- 只考虑到 \((10^{t+1}k + 3\times 10^t - 1)\) 和 \((10^{t+1}k + 3\times 10^t)\) 一种的;
- 将上述第二种误作为 \((10^{t+1}k + f\times 10^t - 1)\) 和 \((10^{t+1}k + f\times 10^t)\) 其中 \(f \in [1, 2, 4, 5, 6, 7, 8, 9]\) 的;
- 在 \(l, r\) 位数不同时直接输出 \(2\) 的;
- 以上情况应均在数据中获得 恰好 \(0\) 分的高贵成绩(待检验,但是我可以保证 CSP-S 当天在学校大巴上或者这之前我可以完成这项工作)。
- 当然,正解不需要判断这么多,因为可以直接暴力依次模拟 \(r - l\) 个数遇到 \(2\) 就 break,而每 \(10\) 个中就一定会至少产出一个 \(2\),时间复杂度当然可以保证。
Cfz Round 3
-
B 题:
- idea, data, solution from irris
- 出题过程:之前想过很多次,关于题面中的 \(f(u)\),也就是「每一位的权值异或」的权值可能会产生一些什么样的题目,但是在思索一些时候后发现没有充分好的想法,于是就把它做成了一道偏简单题的形式,丢了出来。
- 解法知识点及题目亮点:
- 了解常见的位运算以及性质、运算律就不难得出本题的做法。
- 我认为这是一道够格的小清新签到题,甚至在于要求选手们用二进制形式输出答案,给予了充足的提示。
- 依然存在一些细节上的考察,比如答案为 \(0\) 或 \(2^n\) 可能存在的特殊处理。
- 可能类似的题目与查重细节:
- 没有刻意地进行大规模的查重,但是没有得到验题人反映有比较类似的题目存在的反馈。
- 部分分设计:
- 无
- 可能的非正解及数据强度:
- 一般来说,前缀异或和判错是显然没有放过的。
- 数据中涉及到了上述「答案为 \(0\) 或 \(2^n\)」或者 \(n = 1\) 或者有无 \(+1\) 等情况,以确保细节错误的代码一定不能通过。
-
E 题:
- idea from 251Sec & irris, data, solution from irris
- 出题过程:一开始 251Sec 在隔壁只有四个人(我,251Sec,TernaryTree,0x3b800001,不幸的是这个群已经被解散了)的群里丢了个 \(k = 0\),感觉这个题很有意思,很签到,然后就拓展了一下!结果 251Sec 是个摆烂人(他好像也没咋细想这题),这题就全权丢给我了!本来题面背景里有小 R、小 L(代指 251Sec)的,但是可能出于历史原因,可能小 L 不太吉利啥的,反正给删掉了(
- 解法知识点及题目亮点:
- 对于 \(k = 0\) 的观察很有趣,因为每个位置是否合法是完全独立的;
- 对于 \(k = 1\) 以及拓展到 \(k \geq 2\),分别需要结合扫描线思想处理「对于所有区间」问题,以及运用组合数优化一个经典的问题。
- 比较 Educational,可能?
- 可能类似的题目与查重细节:
- \(f(S)\) 本身应该没有重题(因为就是 2B 难度的签到),后半部分有一个比较相似的题,括号(这个题目似乎很不知名;而且,评蓝有点过分了吧)。
- 部分分设计:
- 无
- 可能的非正解及数据强度:
- 数据中存在 \(k = 0, 1, 10^{18}\) 等极端情况。
- 理论上,随意生成的 \(\tt 01\) 串应当足够强,所以我几乎没有特意构造,除了加了极少量聊胜于无的全 \(\tt 0\) 与全 \(\tt 1\) 串,防止一些选手可能挂在莫名其妙的边界上但是却 AC 了。
JRKSJ R8
-
A 题,网球:
- 出题人 irris。验题人 critnos, cyffff。
- 出题过程:
- 因为最近我个人需要大量简单题,所以随便捏了一些 div2 A 题。感觉只能从神秘 math 之类的入手了。因此就在物理二模 AK 完无聊的时候对着试卷上的图片想到了这题。
- 题目名来源于现实生活中的网球,虽然和这道题的命制过程没有太多联系。
- 解法涉及的知识点和题目亮点:
- 考察了选手简单的解小学六年级数学应用题的能力,以及普及组选手需要的快速求 \(\gcd(x, y)\) 的能力。
- 感觉是题目描述比较小清新的 easy math。
- 可能类似的题目与查重细节:
- 应该是没有直接找到「给你一个 \(a : b\) 满足某某比例」,求 \(a, b\)......之类的原题。
- 部分分设计:
- \(v \leq 20\) 是给暴力枚举,\(\mathcal O(v^2)\) 分数的。
- \(v \leq 5000\) 是给正解爆 int 或者不会求 gcd、暴力枚举最简整数比之类分数的,甚至于复杂度低于 \(\mathcal O(v^2)\) 但又不是 \(\mathcal O(\log v)\) 的。
- \(v \leq 10^6\) 做法未知,但是万一有人只过到这个范围呢。
- 可能的非正解与测试数据强度:
- 没有除以 gcd、没有开 64 位整数、向上下取整没有取对等,应该都不能得到分数。
- critnos 添加了一个卡掉暴力质因数分解的 #8,可能有一定的意义(虽然看上去会暴力质因数分解的应该都能够会求 gcd)。
-
D 题,C0mp0nents
- 出题人 irris。验题人 critnos, cyffff。
- 出题过程:
- 想到了一些在图上赋权值的神秘问题,于是想出了这个题,然后从 \(k = 1\) 套了个 \(k \geq 1\) 的包装。
- 一开始是一棵树,后来发现 std 和这个完全无关,就改成了一张图。
- 解法涉及的知识点和题目亮点:
- 考察了选手抽象图论连通性的能力,以及通过单调性简单优化二维数点等 log 做法至线性的能力。
- 我没有见过这种看起来是图论题,实际上和图完全没有关系的神秘厉害题!
- 可能类似的题目与查重细节:
- 在 Is my problem new? 上进行了一些搜索,得到的结果与本题的核心操作、所求量等驴头不对马嘴。
- 部分分设计:
- 分别是:未知的 \(\mathcal O(n^2m)\) 做法、\(\mathcal O(nm)\) 做法、\(\mathcal O(m\log n)\) 做法以及 \(\mathcal O(n + m)\) 做法。
- 可能的非正解与测试数据强度:
- 二维数点枚举每条边、最劣复杂度平方求 \(L_i, R_i\) 都在后两个 Subtask 里被卡掉了,只能获得 \(nm\) 的分数。
- 感觉测试数据应该是把答案很大、答案很小都包括进去了,AC 做法的正确性肯定能够保证;时间复杂度的问题上,\(\mathcal O(m\log n)\) 跑了大约 1.4s 左右,而且卡常空间有限,加上赛时评测量大,应当无法通过 1s 的限制。
- 有验题人因为神秘原因线性被卡常了,但是换用链式前向星而不是 vector 后就轻松在 0.5 倍时限内过去了。其实现在的 std 已经能够跑到 0.2s 左右,但是考虑到常数问题,开 1s 吧。
2024.11
由出题组填写
基本信息:
- 月赛名称:/
- 月赛类型:小月赛。
- 负责人:irris,419487。
- 题目数量:4 题。
- 期望比赛时长:4 小时。
比赛工作人员汇总:
- idea 提供者:
- irris,419487
- 其他参与比赛工作的人员:
- 关键的验题人 Skywave, IvanZhang2009, wosile, 251Sec, N_z_, Daniel_lele, jason_sun
- 不关键的验题人 交审团队里的其余成员
题目信息:
- A 题,《碧树》:
- 出题人:irris
- 工作人员:idea, solution, std, data by irris; check by Skywave
- 出题过程:他,亦狂亦侠亦温文。树,树树树树树树树。
- 额外来源备注:/
- 部分分设计:
不大会设计这个题的部分分,所以变成了 100 分的捆绑测试\(k = 2\),引导选手从 \(k = 1\) 入手,得到只需要在链上再插入一片叶子的事实,最后得到正解;\(a_i\) 全相等是类似的,也能得到一定的结论,而且树长得很像一个扫帚,有点莫名的喜感。分值选择了 30 - 30 - 40,可能对新手比较友好(?) - 造数据方式:使用下面的一些 gen 生成
gen-rand
#include "testlib.h"
#include <bits/stdc++.h>
signed main(signed argc, char* argv[]) {
registerGen(argc, argv, 1);
int k = opt<int>("k"), v = opt<int>("v");
std::cout << k << '\n';
for (int i = 1; i <= k; ++i)
std::cout << rnd.next(2, v) << " \n"[i == k];
return 0;
}
gen-same
#include "testlib.h"
#include <bits/stdc++.h>
signed main(signed argc, char* argv[]) {
registerGen(argc, argv, 1);
int k = opt<int>("k"), v = opt<int>("v");
std::cout << k << '\n';
for (int i = 1; i <= k; ++i)
std::cout << v << " \n"[i == k];
return 0;
}
script
gen-rand -k=2 -v=3000 > $
gen-rand -k=2 -v=100000 > $
gen-same -k=87564 -v=99999 > $
gen-same -k=100000 -v=99824 > $
gen-rand -k=10000 -v=500 > $
gen-rand -k=68017 -v=52888 > $
gen-rand -k=43288 -v=97533 > $
gen-rand -k=100000 -v=100000 > $
gen-rand -k=1 -v=100000 > $
- B 题,《繁花》:
- 出题人:irris
- 工作人员:idea, solution by irris & IvanZhang2009; std, data by irris; check by wosile
- 出题过程:一开始得到一个求每一对 \((i, j)\) 偏序关系是什么的题(\(n \leq 2\times 10^3\)),然后发现直接求 \(\texttt{<>=}\) 总个数的做法甚至比这个更优美,就拿了出来
- 额外来源备注:/
- 部分分设计:Subtask 分别对应:\(\mathcal O(\sum f(n) n^2)\),这表示 dfs 所有可能合法的值域为 \([1, n]\) 的序列;\(\mathcal O(\sum n^2)\),相当于扫 \(n\) 遍;不存在 \(\texttt =\),可能存在一些写挂了的做法。因为得到 \(n^2\) 后思考 \(\mathcal O(n)\) 应当不是很难,只需要一个简单的双指针状物,所以分值设计为 10 - 20 - 20 - 50。
- 造数据方式:使用下面的一些 gen 生成
gen-rand-lim
#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;
int main(int argc, char **argv) {
registerGen(argc, argv, 1);
int t = opt<int>("t"), sumN = opt<int>("n"), p = opt<int>("p"), q = opt<int>("q"), r = opt<int>("r"), L = opt<int>("l");
bool flex = opt<bool>("f");
auto N = rnd.partition(t, sumN, 2);
printf("%d\n", t);
for (int i = 0, P, Q, R; i < t; ++i) {
if (N[i] > L) N[i] = L;
printf("%d\n", N[i]);
P = p, Q = q, R = r;
if (flex) {
do P = std::max(rnd.next(p - 5, p), 0), Q = std::max(rnd.next(q - 5, q), 0), R = std::max(rnd.next(r - 5, r), 0); while (P + Q + R == 0);
} for (int j = 1, seed; j < N[i]; ++j) {
seed = rnd.next(1, P + Q + R);
putchar(seed <= P ? '<' : seed <= P + Q ? '=' : '>');
} puts("");
}
return 0;
}
gen-rand
#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;
const int L = 200'000;
int main(int argc, char **argv) {
registerGen(argc, argv, 1);
int t = opt<int>("t"), sumN = opt<int>("n"), p = opt<int>("p"), q = opt<int>("q"), r = opt<int>("r");
bool flex = opt<bool>("f");
auto N = rnd.partition(t, sumN, 2);
printf("%d\n", t);
for (int i = 0, P, Q, R; i < t; ++i) {
if (N[i] > L) N[i] = L;
printf("%d\n", N[i]);
P = p, Q = q, R = r;
if (flex) {
do P = std::max(rnd.next(p - 5, p), 0), Q = std::max(rnd.next(q - 5, q), 0), R = std::max(rnd.next(r - 5, r), 0); while (P + Q + R == 0);
} for (int j = 1, seed; j < N[i]; ++j) {
seed = rnd.next(1, P + Q + R);
putchar(seed <= P ? '<' : seed <= P + Q ? '=' : '>');
} puts("");
}
return 0;
}
script
gen-rand-lim -t=8 -n=1000 -l=8 -p=10 -q=10 -r=10 -f > $
gen-rand-lim -t=8 -n=1000 -l=8 -p=3 -q=3 -r=3 -f > $
gen-rand-lim -t=8 -n=1000 -l=8 -p=5 -q=5 -r=2 -f > $
gen-rand-lim -t=8 -n=1000 -l=8 -p=5 -q=2 -r=5 -f > $
gen-rand-lim -t=8 -n=8000000 -l=5000 -p=10 -q=10 -r=10 -f > $
gen-rand-lim -t=8 -n=8000000 -l=5000 -p=3 -q=3 -r=3 -f=1 c6ea312469026e86538c08c0f2eb1d6e > $
gen-rand-lim -t=8 -n=8000000 -l=5000 -p=6 -q=4 -r=6 -f > $
gen-rand-lim -t=8 -n=8000000 -l=5000 -p=3 -q=6 -r=4000 > $
gen-rand -t=10000 -n=500000 -p=5 -q=0 -r=5 -f > $
gen-rand -t=100 -n=500000 -p=5 -q=0 -r=5 -f > $
gen-rand -t=8 -n=500000 -p=3 -q=0 -r=3 -f > $
gen-rand -t=2 -n=500000 -p=10000 -q=0 -r=1 > $
gen-rand -t=2 -n=500000 -p=3 -q=0 -r=50000 > $
gen-rand -t=10000 -n=500000 -p=5 -q=5 -r=5 -f > $
gen-rand -t=100 -n=500000 -p=5 -q=5 -r=5 -f > $
gen-rand -t=8 -n=500000 -p=3 -q=3 -r=3 -f > $
gen-rand -t=3 -n=500000 -p=3 -q=3 -r=3 -f > $
gen-rand -t=3 -n=500000 -p=2 -q=2 -r=3 -f > $
gen-rand 168 -t=2 -n=500000 -p=3 -q=3 -r=5 -f > $
gen-rand 211 -t=2 -n=500000 -p=3 -q=3 -r=4 -f > $
gen-rand 343 -t=2 -n=500000 -p=6 -q=6 -r=6 -f > $
gen-rand -t=2 -n=500000 -p=50000 -q=20 -r=1000 d190901aa472b160b7d6c20738d70d04 > $
gen-rand -t=2 -n=500000 -p=20 -q=50000 -r=1000 15ffcc6be9fad8be9a93cf5b0048fc9e > $
gen-rand -t=2 -n=500000 -p=4 -q=1000 -r=50000 c6ea312469026e86538c08c0f2eb1d6e > $
gen-rand -t=2 -n=500000 -p=50000 -q=1000 -r=20 81439fd779d558ae9e5368075a9bcb76 > $
- C 题,《吻秋》:
- 出题人:irris
- 工作人员:idea, data by irris; solution, std by wosile; check by wosile, 251Sec
- 出题过程:想到了一些关于排序的二分答案 题目,因此得到了一个简单的二分答案的 Subtask;随后产生了一些奇怪的做法,但是 wosile 给出了一个简洁且优美且复杂度显著更低的做法,也被最终采用了
- 额外来源备注:/
- 部分分设计:
- Subtask 0(9 pts)— 暴力模拟;
- Subtask 1(23 pts)— 与正解无关但复杂度确实可能更优秀的二分答案,\(\mathcal O(q^2\log v)\);
- Subtask 2(20 pts)— \(m \leq 5\),\(q \leq 4\times 10^5\),送给可能的大量讨论乱搞;
- Subtask 3(27 pts)— \(q \leq 4\times 10^5\);
- Subtask 4(20 pts)— 无特殊限制(\(q \leq 5\times 10^6\))。
- p.s. 事实上 Subtask 0~3 都有愚蠢的线段树分裂合并做法,因为实际上相当于什么都不干的合并和分裂操作是 1log 的,所以能够通过 80 pts。Subtask 4 的限制范围下,该做法大概需要花费 5s。
- 造数据方式:使用下面的 gen 生成(随机成分较大,所以应该并没有卡满神秘暴力的样子,,但是能用了应该
#include "testlib.h"
#include <bits/stdc++.h>
#define MAXM 31
bool leq[MAXM][MAXM];
int main(int argc, char* argv[]){
registerGen(argc, argv, 1);
int N = opt<int>("n"), M = opt<int>("m"), Q = opt<int>("q"), V = opt<int>("v"), T = opt<int>("t");
printf("%d %d %d\n", N, M, Q);
std::vector<std::vector<int>> a(M + 1);
for (int i = 1; i <= M; ++i) {
a[i].resize(N + 1);
for (int j = 1; j <= N; ++j) {
a[i][j] = rnd.next(1, V);
printf("%d%c", a[i][j], " \n"[j == N]);
}
}
int rp = opt<int>("rp"), rq = opt<int>("rq");
while (Q--) {
if (rnd.next(1, rq) <= rp) {
printf("2 %d %d\n", rnd.next(1, M), rnd.next(1, N));
} else {
int cnt = 0, x = 1, y = 1;
for (int i = 1; i <= M; ++i) for (int j = 1; j <= M; ++j)
if (leq[i][j]) ++cnt;
if (cnt == 0 || rnd.next(1, T) == 1) {
while (x == y) x = rnd.next(1, M), y = rnd.next(1, M);
} else {
int w = rnd.next(1, cnt);
for (int i = 1; w && i <= M; ++i) for (int j = 1; w && j <= M; ++j)
if (leq[i][j] && --w == 0) {
x = i, y = j; if (rnd.next(0, 1)) std::swap(x, y);
}
} printf("1 %d %d\n", x, y);
leq[x][y] = true, leq[y][x] = false;
for (int i = 1; i <= M; ++i) if (i != x && i != y) {
bool hX = leq[x][i] || leq[i][x], hY = leq[y][i] || leq[y][i];
if (leq[i][x] && !hY) leq[i][x] = false, leq[i][y] = true;
else if (leq[y][i] && !hX) leq[y][i] = false, leq[x][i] = true;
}
}
}
return 0;
}
script
gen -n=10000 -m=10 -q=3000 -v=1000 -t=40 -rp=4 -rq=5 > $
gen -n=10000 -m=20 -q=3000 -v=10000000 -t=40 -rp=1 -rq=2 > $
gen -n=1000000 -m=2 -q=3000 -v=10000000 -t=1 -rp=1 -rq=10 > $
gen -n=666666 -m=3 -q=3000 -v=10000000 -t=400 -rp=1 -rq=5 > $
gen -n=400000 -m=5 -q=3000 -v=10000 -t=200 -rp=1 -rq=2 > $
gen -n=200000 -m=8 -q=3000 -v=100000 -t=100 -rp=5 -rq=11 > $
gen -n=153846 -m=13 -q=3000 -v=5000000 -t=70 -rp=9 -rq=23 > $
gen -n=100000 -m=20 -q=3000 -v=10000000 -t=40 -rp=1 -rq=2 > $
gen -n=1000000 -m=2 -q=400000 -v=10000000 -t=1 -rp=3 -rq=5 > $
gen -n=500000 -m=4 -q=400000 -v=800000 -t=30000 -rp=1 -rq=2 > $
gen -n=400000 -m=5 -q=400000 -v=10000000 -t=20000 -rp=3 -rq=5 > $
gen -n=200000 -m=10 -q=400000 -v=100000 -t=2000 -rp=3 -rq=5 > $
gen -n=133333 -m=15 -q=400000 -v=5000000 -t=2000 -rp=3 -rq=5 > $
gen -n=100000 -m=20 -q=400000 -v=5000000 -t=2000 -rp=3 -rq=5 > $
gen -n=100000 -m=20 -q=400000 -v=10000000 -t=2143 -rp=1 -rq=2 > $
gen -n=100000 -m=20 -q=5000000 -v=10000000 -t=40000 -rp=1 -rq=5 > $
- D 题,《残雪》:
- 出题人:irris
- 工作人员:idea, solution, std, data by irris; check by N_z_, Daniel_lele, wosile, jason_sun
- 出题过程:翻到了一个古老的给出 \(n + m, L, R\) 计数合法 \(t\) 个数的题目,发现这是近乎不可做的,但是在思考判定答案是否为 \(0\) 时得到了一些有趣的结论,就出了出来
- 额外来源备注:/
- 部分分设计:Subtask 分别对应:\(\mathcal O(q{n + m \choose n}(n + m)(R - L))\)(13 pts),观察到结论后的 \(\mathcal O((n + m)^2)\)(13 + 20 = 33 pts)和 \(\mathcal O(n + m)\)(13 + 20 + 13 = 46 pts),以及 \(L = R\) 时可能存在的猜测循环节做法(13 pts),引导选手尝试探索正解。
- 造数据方式:使用下面的好多 gen 生成
gen-small-cases
#include "testlib.h"
#include <bits/stdc++.h>
bool judge(int L, int R, int n, int m) {
if (n == 0 || m == 0) return true;
if (L == 1) return false;
if (n > m) std::swap(n, m);
int t = (n - 1) / (L - 1), r = n - t * (L - 1);
int need = t * (L + 1) + std::min(r - 1, t * (R - L));
return need <= m;
}
struct Node {
int L, R, p, q;
Node () {}
Node (int L0, int R0, int P, int Q) : L(L0), R(R0), p(P), q(Q) {}
};
int main(int argc, char* argv[]) {
registerGen(argc, argv, 1);
int Q = opt<int>("q"), sum = opt<int>("s");
printf("%d\n", Q);
std::vector<Node> yes, no;
for (int n = 0; n <= sum; ++n) for (int m = 0; n + m <= sum; ++m) if (n + m >= 1)
for (int L = 1; L <= std::min(n, m) + 1; ++L) for (int R = L; R <= std::max(n, m); ++R)
if (judge(L, R, n, m)) yes.push_back(Node(L, R, n, m)); else no.push_back(Node(L, R, n, m));
while (Q--) {
auto u = yes[rnd.next(0u, yes.size() - 1)];
if (rnd.next(0, 1)) u = no[rnd.next(0u, no.size() - 1)];
printf("%d %d %d %d\n", u.L, u.R, u.p, u.q);
}
return 0;
}
gen-summax
#include "testlib.h"
#include <bits/stdc++.h>
using ll = long long;
int gNeed(int n, int L, int R) {
int t = (n - 1) / (L - 1), r = n - t * (L - 1);
return t * (L + 1) + std::min((ll)r - 1, (ll)t * (R - L));
}
int main(int argc, char* argv[]) {
registerGen(argc, argv, 1);
int Q = opt<int>("q"), B1 = opt<int>("b1"), B2 = opt<int>("b2"), sumV = opt<int>("v");
int p = opt<int>("rp"), q = opt<int>("rq"), T = opt<int>("t"); // p/q prob use v2.
std::vector<int> maxN(Q, 1); sumV -= Q - rnd.next(0, T);
while (sumV--) ++maxN[rnd.next(0, Q - 1)];
printf("%d\n", Q);
for (int i = 0; i < Q; ++i) {
int m = maxN[i], b = m / (rnd.next(1, p) <= q ? B2 : B1);
int L = rnd.next(1, b), R = rnd.next(1, b);
if (L > R) std::swap(L, R);
int n0 = 0, n1 = m;
if (L == 1) { n0 = n1 = rnd.next(0, 1) ? 0 : rnd.next(1, m); }
while (n0 < n1) {
int mid = n0 + n1 + 1 >> 1;
if (gNeed(mid, L, R) <= m) n0 = mid; else n1 = mid - 1;
} if (L != 1) n0 = rnd.next(std::max(n0 - 2, 1), std::min(n0 + 2, m));
if (rnd.next(0, 1)) std::swap(n0, m);
printf("%d %d %d %d\n", L, R, n0, m);
}
return 0;
}
std-chk-bound-lr
#include "testlib.h"
#include <bits/stdc++.h>
using ll = long long;
ll gNeed(ll n, ll L) {
return (n - 1) / (L - 1) * (L + 1);
}
int main(int argc, char* argv[]) {
registerGen(argc, argv, 1);
int Q = opt<int>("q"); ll V1 = opt<ll>("v1"), V2 = opt<ll>("v2"), Vn = opt<ll>("vn");
int p = opt<int>("rp"), q = opt<int>("rq"); // p/q prob use v2.
printf("%d\n", Q);
for (int i = 1; i <= Q; ++i) {
ll V = rnd.next(1, q) <= p ? V2 : V1;
ll n = rnd.next(0ll, Vn), L = rnd.next(1ll, V);
if (L == 1) {
ll m = rnd.next(0, 1) && n > 0 ? 0ll : rnd.next(1ll, Vn);
printf("%lld %lld %lld %lld\n", L, L, n, m);
continue;
}
n = std::max(n, 1ll);
if (rnd.next(0, 1))
n = (L - 1) * rnd.next(1ll, (Vn - 1) / (L - 1)) + 1;
ll m = std::min((n - 1) / (L - 1) * (L + 1), Vn);
if (rnd.next(0, 1) && m > n) {
if (rnd.next(0, 2)) {
ll k = rnd.next(1, 10);
m = std::min((__int128)m, (__int128)n + (__int128)k * L);
} m = rnd.next(std::max(n, m - 5), m - 1);
} else {
m = rnd.next(m, std::min(Vn, m + 3));
}
if (rnd.next(0, 1)) std::swap(n, m);
printf("%lld %lld %lld %lld\n", L, L, n, m);
}
return 0;
}
std-chk-bound
#include "testlib.h"
#include <bits/stdc++.h>
using ll = long long;
ll gNeed(ll n, ll L, ll R) {
ll t = (n - 1) / (L - 1), r = n - t * (L - 1);
return t * (L + 1) + std::min((__int128)r - 1, (__int128)t * (R - L));
}
int main(int argc, char* argv[]) {
registerGen(argc, argv, 1);
int Q = opt<int>("q"); ll V1 = opt<ll>("v1"), V2 = opt<ll>("v2"), Vn = opt<ll>("vn");
int p = opt<int>("rp"), q = opt<int>("rq"); // p/q prob use v2.
printf("%d\n", Q);
for (int i = 1; i <= Q; ++i) {
ll V = rnd.next(1, q) <= p ? V2 : V1;
ll n = rnd.next(0ll, Vn), L = rnd.next(1ll, V), R = rnd.next(1ll, V);
if (L > R) std::swap(L, R);
if (L == 1) {
ll m = rnd.next(0, 1) && n > 0 ? 0ll : rnd.next(1ll, Vn);
printf("%lld %lld %lld %lld\n", L, R, n, m);
continue;
}
ll m = std::min(gNeed(n, L, R), Vn);
if (m == 0) m = rnd.next(1ll, Vn);
m = rnd.next(std::max(m - 2, n == 0 ? 1ll : 0ll), std::min(m + 2, Vn));
if (rnd.next(0, 1)) std::swap(n, m);
printf("%lld %lld %lld %lld\n", L, R, n, m);
}
return 0;
}
std-lim-bound
#include "testlib.h"
#include <bits/stdc++.h>
using ll = long long;
ll gNeed(ll n, ll L, ll R) {
ll t = (n - 1) / (L - 1), r = n - t * (L - 1);
return t * (L + 1) + std::min((__int128)r - 1, (__int128)t * (R - L));
}
int main(int argc, char* argv[]) {
registerGen(argc, argv, 1);
int Q = opt<int>("q"); ll V1 = opt<ll>("v1"), V2 = opt<ll>("v2"), Vn = opt<ll>("vn"), d = opt<ll>("d");
int p = opt<int>("rp"), q = opt<int>("rq"); // p/q prob use v2.
ll mod = opt<ll>("mod");
printf("%d\n", Q);
for (int i = 1; i <= Q; ++i) {
ll V = rnd.next(1, q) <= p ? V2 : V1;
ll L = rnd.next(1ll, V), R = rnd.next(std::max(L - d, 1ll), std::min(L + d, V));
if (L > R) std::swap(L, R);
if (L == 1) {
ll n = rnd.next(0ll, Vn);
ll m = rnd.next(0, 1) && n > 0 ? 0ll : rnd.next(1ll, Vn);
printf("%lld %lld %lld %lld\n", L, R, n, m);
continue;
}
ll n = rnd.next(0ll, Vn / (L - 1)) * (L - 1) + rnd.next(0ll, std::min(L - 1, mod));
ll m = std::min(gNeed(n, L, R), Vn);
if (m == 0) m = rnd.next(1ll, Vn);
m = rnd.next(std::max(m - 2, n == 0 ? 1ll : 0ll), std::min(m + 2, Vn));
if (rnd.next(0, 1)) std::swap(n, m);
printf("%lld %lld %lld %lld\n", L, R, n, m);
}
return 0;
}
script
gen-small-cases -q=1000 -s=14 2903820192 > $
gen-small-cases -q=1000 -s=14 5091098270 > $
gen-small-cases -q=1000 -s=14 7795683001 > $
gen-small-cases -q=1000 -s=14 3481901026 > $
gen-small-cases -q=1000 -s=14 0109849205 > $
gen-small-cases -q=1000 -s=14 6829420387 > $
gen-summax -q=400 -v=5000 -b1=1 -b2=3 -rp=1 -rq=2 -t=0 > $
gen-summax -q=50 -v=5000 -b1=2 -b2=5 -rp=1 -rq=2 -t=5 4507b96a255d482761de715c520309c4 > $
gen-summax -q=50 -v=5000 -b1=2 -b2=5 -rp=1 -rq=2 -t=5 c3aa705f5fda00c2f16e1eb74498c503 > $
gen-summax -q=10 -v=5000 -b1=2 -b2=11 -rp=1 -rq=3 -t=3 > $
gen-summax -q=10 -v=5000 -b1=2 -b2=11 -rp=1 -rq=3 -t=4 8a3c09517bc6635d73fd92c9e674994c > $
gen-summax -q=4 -v=5000 -b1=3 -b2=11 -rp=1 -rq=2 -t=0 > $
gen-summax -q=100000 -v=10000000 -b1=2 -b2=12 -rp=1 -rq=8 -t=100 > $
gen-summax -q=1000 -v=10000000 -b1=3 -b2=5 -rp=1 -rq=2 -t=20 > $
gen-summax -q=100 -v=10000000 -b1=4 -b2=30 -rp=1 -rq=3 -t=30 > $
gen-summax -q=10 -v=10000000 -b1=1 -b2=5 -rp=3 -rq=4 -t=42 > $
gen-small-cases -q=100000 -s=110 0454c29ccec7492a64c175516a003158 > $
gen-small-cases -q=100000 -s=30 b2263863a1e17853df096562aa3369aa > $
std-chk-bound-lr -q=100000 -v1=1000 -v2=1000 -vn=10000000 -rp=1 -rq=1 > $
std-chk-bound-lr -q=100000 -v1=1000000 -v2=200 -vn=1000000000000 -rp=1 -rq=20 1c43aa86c9cf5104c774c1bdf244e2d8 > $
std-chk-bound-lr -q=100000 -v1=1000000000 -v2=200 -vn=1000000000000000000 -rp=1 -rq=30 > $
std-chk-bound-lr -q=100000 -v1=2000000000000 -v2=2000 -vn=1000000000000000000 -rp=1 -rq=29 > $
std-chk-bound-lr -q=100000 -v1=4000000000000000 -v2=20000 -vn=1000000000000000000 -rp=1 -rq=50 > $
std-chk-bound-lr -q=100000 -v1=1000000000000000000 -v2=10000000 -vn=1000000000000000000 -rp=2 -rq=37 > $
std-chk-bound -q=100000 -v1=1000 -v2=15 -vn=2000 -rp=1 -rq=30 > $
gen-small-cases -q=100000 -s=150 > $
std-chk-bound-lr -q=100000 -v1=4000000000000000 -v2=20000 -vn=1000000000000000000 -rp=1 -rq=10 > $
std-lim-bound -q=100000 -v1=10000000000000000 -v2=1000000000 -vn=100000000000000000 -rp=1 -rq=15 -d=1000000 -mod=50000 2c9f0d32ede5bcd18550f773112aa4c4 > $
std-lim-bound -q=100000 -v1=100000000000000000 -v2=10000000000 -vn=1000000000000000000 -rp=1 -rq=15 -d=10000000 -mod=500000 > $
std-chk-bound -q=100000 -v1=1000000000000000000 -v2=100000000000000 -vn=1000000000000000000 -rp=1 -rq=15 > $
由审核员填写
审核员:【写用户名,双审写两个】
对比赛的评价:【双审两位审核员分开写,可以包含质量打分,点出个别题目的亮点,对难度分布的评价,一些不足之处等】
附:官方比赛规范中的相关文本要求。
审核流程:
- 明确所有参与比赛工作的人员,包括负责人、出题人、验题人、造数据人等,不包括月赛审核员和仅知道题目 idea 而不参与比赛工作的人(但需做好保密工作)。所有人都需要在洛谷进行奖项认证(非 OI 选手可以通过其他途径确认身份,但需要额外报备),总人数不得超过题目数的 2 倍。
- 明确所有知道题目 idea 的人,无论他们是否参与比赛工作。
- 明确题目来源,包括出题人资格和 idea 形成的过程,其中出题人原则上要求蓝金钩,非蓝金钩的出题人需提供出题履历并由月赛审核员额外审查。
- 明确题目质量和知识点分布。
- 明确题目难度,要求第一题的通过率不低于 50%,前两题涉及的知识点需在 CCF 大纲入门级范围内,前四题涉及的知识点需在 CCF 大纲入门级和提高级范围内。
- 与出题人共同进行查重。
- 考量部分分和测试数据的设计。
- 组题。可以留有备用题,用于赛前特殊情况下更换。