2024牛客多校7&8
7
A Maximum Subarray Sum (A)
(出题人解法没看明白)
解法2的切入点类似之前某场div2的D题(题解传送门),将操作过程视为选出 \(\lfloor n/k\rfloor\) 个长度为 \(k\) 的子序列,答案序列中大于 \(k\) 的部分暂时与舍弃部分等价,即最后舍弃的数字下标满足 \(i' = i\space mod\space k\). 设 \(dp[i][x][y][p]\) 表示到 \(i\) 位置,换走 \(x\) 个数、换入 \(y\) 个数,当前选择状态为 \(z\)(选或不选)时的最大和,每个计入答案的数可能存在按序选入/中途换入两种选择方式,同理未计入答案的数也存在直接抛弃/换出两种可能。当前数被选中时,由于子序列长度至少为 \(k\),为避免出现间断点,\(dp[i][x][y][1]\) 只能由 \(dp[i - k + 1][x][y][1]\)(长度为 \(k\) 的子序列)或 \(dp[i - 1][x][y][1]\)(答案序列中大于 \(k\) 的部分)转移而来,前者需要从 \(0\) 到 \(m\) 枚举换出数字的数量,其他所有情况则可以 \(O(1)\) 转移。利用前缀和与set之类的容器处理换出后的序列最大值,整体复杂度可优化至 \(nm^3\) 左右。特别地,当 \(i\space mod\space k = k - 1\) 时,由于舍弃的数不可能超过 \(k - 1\) 个,该位置上的数字不能被直接抛弃,只进入 \(0\) 至 \(m\) 的循环、讨论可能存在的换出情况。代码还是借鉴其他队答案才写明白的,所以没必要放这里了)
I Fight Against the Monster (I)
题解思路是二分答案,不过不用二分也行。\(h\leq m\) 时显然答案为 \(h\),对于 \(h > m\) 的情况,若生产出的 \(k\) 台新机器仍不足以将怪物杀死,可以选择直接补充与剩余血量相等的机器数目(\(h'\leq m - k\) 时),或补充 \(m - k\) 台机器至恰好足以生产新机器,依此思路可 \(O(1)\) 计算答案,代码如下:
if(h <= m) printf("%lld\n", h);
else if(m <= k) printf("%lld\n", m);
else {
ll sum = h - m;
if(sum <= k) printf("%lld\n", m);
else {
ll cnt = sum / m, x = sum % m;
if(x >= k) printf("%lld\n", m + cnt * (m - k) + (x - k));
else printf("%lld\n", m + cnt * (m - k));
}
}
8
上周四不在家所以没打,后来自己写的时候各种卡题,难过)
A Haitang and Game (A)
事实上A题并不是一道博弈题,最终序列中加入的所有 \(d\) 是固定的,不存在任何可能改变胜负的博弈策略。具体而言,若序列中 \(d\)(本身不存在于序列中)的倍数有 \(a_i,a_j,...\),且 \(gcd(a_i,a_j,...) = d\),则 \(d\) 必然会被加入。数据范围 \(a_i\leq 10^5\),遍历 \(1\) 至 \(a_{max}\),枚举倍数判断其是否为合法的 \(d\),由调和级数得整体复杂度 \(nloga_{max}\).
E Haitang and Math (E)
由 \(m\leq n\leq 10^{12}\) 得 \(s(m)\leq 108\),对 \([n - 108, n]\) 枚举因数即可。直接 \(T*100\sqrt n\) 似乎过不了,题解使用区间筛可优化至 \(T(\frac{\sqrt n}{log\space n} + d(n)log\space n)\),但不幸的是我不会区间筛,也没看懂队友的模板,数学题真就只能靠队友了吗TAT
J Haitang and Triangle (J)
可爱构造题,首先考虑无解,\(1\) 在任何情况下都不能与其他数组成三角形,合法子序列的总数不可能超过 \(n - 3\) 个,当 \(m = n - 2\) 时,答案不存在,特判无解。当 \(x > 1\) 时,从 \(x\) 到 \(x + k\) 的顺序序列必然存在 \(k - 2\) 个合法的子序列,而不合法子序列的构成则相对复杂,因此优先考虑对不合法部分的构造。当 \(a + b\leq c\) 时 \(a,b,c\) 不能构成三角形,为构造尽可能多的此类组合,应尽可能选择排列中最小/最大的数。
由此可得构造策略:假设 \(n - m - 2 = 3k\),先保留前 \(2k\) 与后 \(k\) 个数,取出中间 \(m + 2\) 个数,顺序排列形成 \(m\) 个合法子序列;将前 \(2k\) 与后 \(k\) 个数依次组合,诸如 \(1,2k,x,2,2k - 1,x + 1,3,...\) 相邻三个数中,较小数之和只可能为 \(2k + 1\) 或 \(2k + 2\),由于 \(m + 2\geq 2\),必有 \(x > 2k + 2\),即任何子序列必然不合法。该部分以 \(1\) 开头、位于顺序排列的 \(m + 2\) 个数后,\(1\) 的特殊性质可以确保合法子序列不会增加。对于 \((n - m - 2)\mod 3 = 2\),相当于前 \(k\) 个数中额外加入两个,同理构造即可;\((n - m - 2)\mod 3 = 1\) 时,先假设 \(m' = m - 1\) ,构造余数为 \(2\) 的情况;再交换后部分中 \(1\) 与 \(2k\) 的位置,此时后部分性质保持不变,由前部分顺序排列可知,相邻两数之差 \(1 < 2k\),\(2k\) 必然能够与 \(x - 2, x - 1\) 形成新的合法子序列。
主要代码:
if(m == n - 2) {
printf("-1\n");
return;
}
int num = m + 2, las = n - (m + 2);
int pre = las / 3 * 2, suf = las / 3;
bool flag = 0;
if(las % 3 == 1) {
num--;
las++;
flag = 1;
}
if(las % 3 == 2) {
pre += 2;
}
for(int i = pre + 1; i + suf <= n; i++) {
ans[i - pre] = i;
}
for(int i = num + 1; i <= n; i += 3) {
int x = (i - num) / 3;
ans[i] = x + 1;
ans[i + 1] = pre + 1 - ans[i];
ans[i + 2] = x + pre + n - las + 1;
if(x == 0 && flag) swap(ans[i], ans[i + 1]);
}
for(int i = 1; i <= n; i++) {
printf("%d ", ans[i]);
}
另有:这题写完交上去两次都WA,一度怀疑我的构造假了(关键题解还不是这么构造的,更害怕了),无奈求助队友才发现我代码的一个弱智错误。。。不好评价,并且再次感谢xht orz