• [CF1223G/1240E]Wooden Raft 题解


    前言

    上午一场模拟赛(发布前很久,因为这题题解太长了),发现T3特别珂怕,打开题解,发现一行字:

    不要再问了,再问就是CF 1240E

    当场去世.jpg。
    在下文中,我们记 (A)(a) 数组中的最大值,在代码中就是 "_max" 。

    题意简述

    题目链接
    给出一组 (n) 块木板以及它们的长度 (a_i),现在要切割出两块木板使之长度为 (x) ,切割 (x) 块木板使之长度为 (y)
    (x imes y) 的最大值。

    题解

    基本思想

    我们有一个非常美妙的使用二分的 (Theta(Aln_Alog_A)) 的做法,
    但是我们今天要说的是一个比它优秀的 (Theta(Aln_A)) 的算法。
    为什么说是 (ln_A) ,因为 (sum_{i=1}^{n} n / i = ln_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 imes 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 which less equal than i
            //l2 - the second longest wood which 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;
    }
    
  • 相关阅读:
    现代程序设计——homework-01
    数据结构 学习笔记03——栈与队列
    MCI 函数与命令
    mciSendString用法
    VC6.0 error LNK2001: unresolved external symbol _main解决办法
    数据结构 学习笔记02——线性表
    数据结构 学习笔记01
    《windows程序设计》学习_4:文本输出,加滚动条
    浅析指针(pointer)与引用(reference)
    指针数组 | 数组指针
  • 原文地址:https://www.cnblogs.com/linzhengmin/p/11794875.html
Copyright © 2020-2023  润新知