前言
上午一场模拟赛(发布前很久,因为这题题解太长了),发现T3特别珂怕,打开题解,发现一行字:
不要再问了,再问就是CF 1240E
当场去世.jpg。
在下文中,我们记 \(A\) 为 \(a\) 数组中的最大值,在代码中就是 "_max" 。
题意简述
题目链接
给出一组 \(n\) 块木板以及它们的长度 \(a_i\),现在要切割出两块木板使之长度为 \(x\) ,切割 \(x\) 块木板使之长度为 \(y\)。
求 \(x \times y\) 的最大值。
题解
基本思想
我们有一个非常美妙的使用二分的 \(\Theta(Aln_Alog_A)\) 的做法,
但是我们今天要说的是一个比它优秀的 \(\Theta(Aln_A)\) 的算法。
对于几乎所有的做法,都有两个显然的思想,
枚举 \(y\) ,然后计算 \(x\);
贪心地把长度为 \(l\) 的木板分解为 \(l = tx + ky + \delta, t \in [0, 2]\) 其中 \(\delta\) 越小越好。
有了这个思想以后我们还需要按照 \(l / y\) 的值对木板分块(即块的区间 \([ ky, (k+1)y )\) )。
接下来我们可以来进行一波分类讨论。
两个 \(x\) 在同一块木板中被切割出
我们记 \(p_1, p_2\) 为两个点,且 \(p_1\) 距离它所在块的右端点比 \(p_2\) 更远。
可以证明, \(p_1\) 的决策一定劣于 \(p_2\) ,因为在抛去 \(ky\) 的情况下,
选择 \(p_2\) 所得的 \(x\) 显然更大。
那么我们从右往左扫描,设当前的块为 \(c\) 。
在式子 \(l = x + ky + \delta\),若当前的块为 \(c\) ,当前块往右的所有块(包括自己)里所在块右端点最远的点为 \(p\) 。
则定义\(x = (c \times y + p \% y) / 2\) ,剩下的部分给 \(ky\) 。
记 \(p\) 在 \(c\) 中的映射为 \(p'\),即 \(p' \in c\) 且 \(p' \equiv p \mod(y)\)(下文中的 \(p_1',p_2'\) 同理)。
具体如上图所示。
两个 \(x\) 分别在两块木板中被切割出
根据上面的结论,我们仍然从右往左扫描,设当前的块为 \(c\)。然后我们记,
当前块往右的所有块(包括自己)里所在块右端点最远的木板为 \(p_1\) 。
当前块往右的所有块(包括自己)里所在块右端点次远的木板为 \(p_2\) 。
那么我们总结出并讨论如下四种情况:
情况1、2:
\(\quad \quad\)
我们在情况一内分类讨论,发现只有两种情况可能最优,
第一种是把 \(p_2\) 木板分解完全(即 \(=x + ky\),\(k\) 为 \(c\) 到 \(p_2\) 所在块跨越的块数),第二种是把 \(p_1\) 木板分解完全。
但是注意到如果 \(p_1\) 木板分解完全,那么 \(p_2\) 木板长度显然不够了,于是我们牺牲 \(p_2\) 木板裁出的一个 \(y\) ,来保证能够切除一个 \(x\) 。
再来考虑情况二,我们发现情况二的不同之处是, \(p_2\) 木板不能够牺牲一个 \(y\) 来保证切除 \(x\) 了(长度不足),
那么处理方法很简单,直接舍弃掉完全分解 \(p_1\) 方案(滑稽),只考虑完全分解 \(p_2\)。
您觉得讨论结束了?还有两种哦 QwQ 。
情况3:
我们发现情况3与情况1相同(显然易见还是将 \(p_1\) 或 \(p_2\) 分解完全)。
情况4
我们发现情况4与情况2相同(显然易见还是不能牺牲 \(p_2\) 的一个 \(y\))。
然后我们注意到计算的时候,\(x\) 不一定小于 \(y\)的个数,所以要取一个 \(min\) 。
代码
note: 我 CodeForces 账号是 \(lukelin\) 哦(所以不要说我的代码是copy的)。
注释是英文的,因为怕出玄学错误。
/* author: lukelin note: I'm sorry that my English is poor. */ #include <cstdio> // input data int a[500005]; // make length -> pos & the prefix int cnt[1000005], prfc[1000005]; int l1[1000005], l2[1000005]; #define min(a,b) ((a<b)?a:b) #define max(a,b) ((a>b)?a:b) inline void swap(int &a, int &b){ int tmp = a; a = b, b = tmp; } long long ans; inline void flush(int x, int cnty, int y){ if (min(cnty, x) <= 1) return ; ans = max(ans, 1ll * min(cnty, x) * y); } int main(){ int n, _max = 0; scanf("%d", &n); for (int i = 1; i <= n; ++i){ scanf("%d", &a[i]), ++cnt[a[i]]; if (_max < a[i]) _max = a[i]; } int prfc_lim = _max << 1; //the max id we will use in prfc for (int i = 1; i <= prfc_lim; ++i) prfc[i] = prfc[i - 1] + cnt[i]; l1[0] = l2[0] = -1; for (int i = 1; i <= prfc_lim; ++i){ //l1 - the longest wood whick less equal than i //l2 - the second longest wood whick less equal than i if (cnt[i] >= 2) l1[i] = l2[i] = i; else if (cnt[i] == 1) l2[i] = l1[i - 1], l1[i] = i; else l1[i] = l1[i - 1], l2[i] = l2[i - 1]; } for (int y = 2; y <= _max; ++y){ int ycnt = 0; for (int c = 1; c * y <= _max; ++c){ //prefix[block c's end] - prefix[block c's begin - 1] ycnt += (prfc[c * y + y - 1] - prfc[c * y - 1]) * c; } // first the situation that both x in one wood int p = -1; for (int c = _max / y + 1; ~c; --c){ // the start position and the end position of current block(c) int blk_sp = c * y, blk_ep = c * y + y - 1; if (l1[blk_ep] >= blk_sp && (p == -1 || p % y < l1[blk_ep] % y)) p = l1[blk_ep]; if (~p) flush((c * y + p % y) >> 1, ycnt - c, y); } // second the situation that each x in one different wood int p1 = -1, p2 = -1; for(int c = _max / y + 1; ~c; --c){ // the start position and the end position of current block(c) int blk_sp = c * y, blk_ep = c * y + y - 1; if(~p2){ //situation 1 - now both p1 and p2 is behind c flush(c * y + p1 % y, ycnt - c * 2 - 1, y); flush(c * y + p2 % y, ycnt - c * 2, y); } if (~p1 && l1[blk_ep] >= blk_sp && l1[blk_ep] % y > p2 % y){ if (l1[blk_ep] % y >= p1 % y){ //situation 3 - p1 is in c, p2 is behind c flush(c * y + p1 % y, ycnt - c * 2, y), flush(l1[blk_ep], ycnt - c * 2 - 1, y); } else{ //situation 4 - p1 is behind c, p2 is in c flush(l1[blk_ep], ycnt - c * 2, y); } } //update if (l1[blk_ep] >= blk_sp && p2 % y < l1[blk_ep] % y) p2 = l1[blk_ep]; if (p1 % y < p2 % y) swap(p1, p2); if (l2[blk_ep] >= blk_sp && p2 % y < l2[blk_ep] % y) p2 = l2[blk_ep]; if (p1 % y < p2 % y) swap(p1, p2); if (~p2){ //situation 2 - p1, p2 is both behind c //* if (p1, p2) isn't both in c, it's useless but won't cause WA flush(c * y + p2 % y, ycnt - c * 2, y); } } } printf("%I64d", ans); return 0; }