• 一本通课后练习 / 高手训练


    扑克牌

    /贪心/堆/二分查找/

    题目链接 一本通

    题目链接 洛谷

    Solution

    可以看到洛谷和一本通上的数据范围有微妙的区别...其中洛谷的算法(算法二)是通用算法,而一本通的算法(算法一)只能过它自己的数据。

    算法一

    每次贪心地把 joker 给当前排数最少的堆,直到没有 joker 或有牌用完,优先队列维护。设排数为 m,时间复杂度约 O(mlogm)。

    算法二

    首先二分,当前答案为 mid。把 joker 看成一堆牌,问题变成了从 n+1 堆牌里面取出 n 个,能否取 mid 次。枚举 1-n,若 (a_i)<mid,统计 (a_i) 与 mid 的差值和 res,这些需要用 joker 补上。若 res <= min(x,m) 则 return true;否则 return false。

    Code

    算法一 Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    using namespace std;
    
    const int N = 2333333;
    int n, m, a[N], cnt = 0;
    priority_queue <int> q;
    
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]), q.push(-a[i]);
        while(!q.empty())
        {
            int a = -q.top();
            q.pop();
            if(cnt < m) a++;
            q.push(-a);
            if(-q.top() - cnt <= 0) break;
            cnt++;
        }
        printf("%d", cnt);
        return 0;
    }
    

    算法二 Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    long long n, m, a[23333], l = 0, r = 1e9;
    
    bool check(long long x)
    {
        long long res = 0;
        for(int i = 1; i <= n; i++)
            if(a[i] - x < 0) res += a[i] - x;
        return (-res) <= min(m, x);
    }
    
    int main()
    {
        scanf("%lld%lld", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        while(l + 1 < r)
        {
            long long mid = (l + r) >> 1;
            if(check(mid)) l = mid;
            else r = mid;
        }
        printf("%lld", l);
        return 0;
    }
    

    楼间跳跃

    题目链接

    Solution

    好不容易做出来一题 QwQ

    这题跟钓鱼那题很像...最优情况下肯定不会从 i+1 号楼走到 i 号楼(即不会往回走),考虑枚举最远到达的楼号 i,那么首先要走 i 步,≤i 的楼都要拿到 h[i] 的价值,可以前缀和维护。经过手玩发现一个神奇的性质,即在同一栋楼 i 中通过走路和坐滑梯,可以在这栋楼中到达任意的 k 层,并且到达时当前楼层中一定能拿到价值(不会浪费时间),然后到达 i+1 号楼。

    所以可以用优先队列来维护前面 m-i 个最大值之和作为额外价值。由于数据范围很大,需要整栋楼地入队和出队。时间复杂度约为 O(nlogn)。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define LL long long
    using namespace std;
    
    const int N = 2333333;
    struct node
    {
        LL w, siz;
        friend bool operator < (node x, node y)
        {
            return x.w > y.w;
        }
    };
    LL n, m, p, Ans = 0, Num = 0, Size = 0, h[N], v[N], sum[N];
    priority_queue <node> q;
    
    inline LL read()
    {
        char ch = getchar(); LL x = 0, f = 1;
        while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
        while(ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
        return f * x;
    }
    
    int main()
    {
        n = read(), m = read();
        sum[0] = 0, m++;
        for(int i = 1; i <= n; i++) h[i] = read(), v[i] = read();
        for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + v[i];
        for(int i = 1; i <= n; i++)
        {
            if(m - i < 0) break;
            q.push((node) { v[i], h[i] - 1 });
            Num += v[i] * (h[i] - 1);
            Size += h[i] - 1, p = Size - (m - i);
            while(p > 0)
            {
                node x = q.top();
                q.pop();
                if(p >= x.siz) Size -= x.siz, Num -= (x.siz * x.w), p -= x.siz;
                else
                {
                    Size -= p;
                    q.push((node) { x.w, x.siz - p });
                    Num -= p * x.w;
                    p = 0;
                }
            }
            Ans = max(Ans, sum[i] + Num);
        }
        printf("%lld", Ans);
        return 0;
    }
    

    SCOI2010 传送带

    /三分/

    题目链接

    Solution

    可以把走法抽象成下面的图:

    A-E-F-D,其中 E 在线段 AB 上,F 在线段 CD 上(均可与端点重合)。

    不难看出 E 的位置作为自变量,与时间的关系是一个二次函数,而对于每一个 E,F 的位置又是一个二次函数。所以基本思路为三分套三分,其中 E、F 点坐标的求解等需要用到相似三角形和一次函数的部分知识。其他的一些细节在代码里。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    
    double ax, ay, bx, by, cx, cy, dx, dy, P, Q, R, eps = 1e-6;
    
    double dis(double x1, double y1, double x2, double y2)
    {
        return (double)(sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
    }
    
    double f(double x)
    {
        double ex, ey;
        if(dis(ax, ay, bx, by) == 0) ex = ey = 0;
        else
        {
            ex = fabs(ax - bx) * (x / dis(ax, ay, bx, by));
            ey = fabs(ay - by) * (x / dis(ax, ay, bx, by));
        }
        ex = max(ex, 0.0), ey = max(ey, 0.0);
        if(ax < bx) ex = ax + ex;
        else ex = ax - ex;
        if(ay < by) ey = ay + ey;
        else ey = ay - ey;
        double l = 0, r = dis(cx, cy, dx, dy), res;
        do
        {
            double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
            double fx1, fy1, fx2, fy2;
            if(dis(cx, cy, dx, dy) == 0) fx1 = fx2 = fy1 = fy2 = 0;
            else
            {
                fx1 = fabs(cx - dx) * (m1 / dis(cx, cy, dx, dy));
                fy1 = fabs(cy - dy) * (m1 / dis(cx, cy, dx, dy));
                fx2 = fabs(cx - dx) * (m2 / dis(cx, cy, dx, dy));
                fy2 = fabs(cy - dy) * (m2 / dis(cx, cy, dx, dy));
            }
            fx1 = max(fx1, 0.0), fy1 = max(fy1, 0.0);
            fx2 = max(fx2, 0.0), fy2 = max(fy2, 0.0);
            if(cx < dx) fx1 = dx - fx1, fx2 = dx - fx2;
            else fx1 = dx + fx1, fx2 = dx + fx2;
            if(cy < dy) fy1 = dy - fy1, fy2 = dy - fy2;
            else fy1 = dy + fy1, fy2 = dy + fy2;
            double len1 = dis(ex, ey, fx1, fy1);
            double len2 = dis(ex, ey, fx2, fy2);
            double F1 = x / P + m1 / Q + len1 / R;
            double F2 = x / P + m2 / Q + len2 / R;
            if(F1 < F2) r = m2;
            else l = m1;
            res = F1;
        } while(r - l >= eps);
        return res;
    }
    
    int main()
    {
        cin >> ax >> ay >> bx >> by >> cx >> cy >> dx >> dy;
        cin >> P >> Q >> R;
        double l = 0, r = dis(ax, ay, bx, by);
        do
        {
            double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
            if(f(m1) < f(m2)) r = m2;
            else l = m1;
        } while(r - l >= eps);
        printf("%.2lf", f(l));
        return 0;
    }
    

    独木桥

    /二分/

    题目链接

    Solution

    可以先看一下洛谷上的独木桥。其中有一个重要的思路:如果把每一个人看成没有区别的点,两个人相遇然后各自转头,相当于他们互相穿过了对方。

    于是我们可以通过初始向左的位置 -T,初始向右的位置 +T算出 T 时间后小孩位置构成的集合 S。

    但是这个做法不能分清具体的小孩,这就需要用到本题的另一个性质:孩子的相对位置不会发生改变。即一个孩子 i,初始时若按照位置由小到大排序,他的排名为 (rank_i),则 T 时刻后他的排名依然为 (rank_i)。因此,问题转化为在集合 S 中求出第 k 小的数。

    对于每一次询问二分答案,左边右边分开排序,统计向左的集合中 pos<mid+T 和向右的集合中 pos<mid-T 的个数,求出当前排名 rk(这一过程还是可以二分)。若 rk>k,r=mid;否则 l=mid。时间复杂度约为 O(nlogn)

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define int long long
    using namespace std;
    
    const int N = 2333333, LEN = 1e10 + 2333;
    struct Child { int p, d, id; } c[N];
    int n, l, r, c1 = 0, c2 = 0, K, T, Q, a[N], b[N], rk[N];
    
    int check(int x, int T)
    {
        int l1 = 0, r1 = c1, a1;
        while(l1 + 1 < r1)
        {
            int mid = (l1 + r1) >> 1;
            if(a[mid] < x + T) l1 = mid;
            else r1 = mid;
        }
        if(a[r1] < x + T) a1 = r1;
        else a1 = l1;
        
        int l2 = 0, r2 = c2, a2;
        while(l2 + 1 < r2)
        {
            int mid = (l2 + r2) >> 1;
            if(b[mid] < x - T) l2 = mid;
            else r2 = mid;
        }
        if(b[r2] < x - T) a2 = r2;
        else a2 = l2;
        return a1 + a2 + 1;
    }
    
    bool cmp(Child x, Child y) { return x.p < y.p; }
    
    signed main()
    {
        scanf("%lld", &n);
        for(int i = 1; i <= n; i++) scanf("%lld", &c[i].p), c[i].id = i;
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld", &c[i].d);
            if(c[i].d == 0) a[++c1] = c[i].p;
            if(c[i].d == 1) b[++c2] = c[i].p;
        }
        sort(a + 1, a + c1 + 1);
        sort(b + 1, b + c2 + 1);
        sort(c + 1, c + n + 1, cmp);
        for(int i = 1; i <= n; i++) rk[c[i].id] = i;
        scanf("%lld", &Q);
        while(Q--)
        {
            scanf("%lld%lld", &K, &T);
            l = -LEN, r = LEN;
            K = rk[K + 1];
            while(l + 1 < r)
            {
                int mid = (l + r) >> 1;
                if(check(mid, T) > K) r = mid;
                else l = mid;
            }
            if(check(r, T) == K) printf("%lld
    ", r);
            else printf("%lld
    ", l);
        }
        return 0;
    }
    

    子集

    /二分/

    题目链接

    Solution

    因为元素的位置对答案没有影响,先对 a 数组进行排序。

    子集的中位数有两种情况:

    若子集大小为 2k,则中位数为 (a_k)

    若子集大小为 2k+1,则中位数为 (frac{a_k+a_{k+1}}{2})

    根据题面,总结出以下两条结论:

    1.价值最大的子集的价值 ≥0(ans 的初值为 0)

    2.子集大小一定为奇数。可以证明若子集大小为 2k,则删掉 (a_{k+1}) 一定不劣(证明在书上,看不懂 QwQ。但是可以猜想后对拍验证)

    首先 O(n) 枚举中位数 x,二分 k,表示从 x 左边选出 k 个,右边选出 k 个(肯定贪心选最大的,和可以用前缀/后缀和维护)。设 check(i) 表示选择 i 的最终答案,若 check(k)<check(k-1),显然若 k 继续增大答案一定不会变优,此时 r=mid;否则 l=mid。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N = 233333;
    int n, a[N], sum[N];
    
    double Ans = 0.0;
    
    double get(int x, int k)
    {
        double a1 = sum[x - k], a2 = sum[x + 1], a3 = sum[n - k + 1];
        return (double)(a1 - a2 + a3) / (2 * (k * 1.0) + 1.0);
    }
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        sort(a + 1, a + n + 1);
        sum[n + 1] = 0;
        for(int i = n; i >= 1; i--) sum[i] = sum[i + 1] + a[i];
        for(int i = 2; i <= n; i++)
        {
            double x = a[i];
            int l = 1, r = min(n - i, i - 1);
            while(l + 1 < r)
            {
                int mid = (l + r) >> 1;
                if(get(i, mid) < get(i, mid - 1)) r = mid;
                else l = mid;
            }
            Ans = max(Ans, max(get(i, l) - x, get(i, r) - x));
        }
        printf("%.5lf", Ans);
        return 0;
    }
    

    friends

    /字符串哈希/

    题目链接

    Solution

    注意书中计算一段连续区间 (C' = c_{k+1}, c_{k+2}, ..., c_{k+n}) 的哈希值公式为 (H(C')=H(C, k+n)-H(C, k) * b^n)。考虑枚举插入的字符 (x),它一定把 2 个 S 中的其中一个拆成了两半,这时候我们需要根据公式把它拼起来。设前一段为 S1,长度为 l1,后一段为 S2,长度为 l2,则该段拼合后的哈希值为 (H(S1)*b^{l2}+H(S2))

    计算和讨论过程相对复杂,注意,输出 NOT UNIQUE 当且仅当存在两个不一样的 S,而不是存在两个不一样的 x 或存在两个不一样的 posx

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    
    const int N = 2333333;
    const long long h1 = 1e9 + 7, h2 = 1e9 + 9, b = 131;
    long long n, len, pos = -1, ans1, ans2;
    long long a[N], b1[N], b2[N], s1[N], s2[N];
    string s, res = "";
    
    long long get1(long long l, long long r)
    {
        if(r < l) return 0;
        return ((s1[r] - (s1[l - 1] * b1[r - l + 1]) % h1) % h1 + h1) % h1;
    }
    
    long long get2(long long l, long long r)
    {
        if(r < l) return 0;
        return ((s2[r] - (s2[l - 1] * b2[r - l + 1]) % h2) % h2 + h2) % h2;
    }
    
    int main()
    {
        cin >> n >> s;
        if(n % 2 == 0)
        {
            printf("NOT POSSIBLE");
            return 0;
        }
        len = (n - 1) / 2;
        for(int i = 0; i < n; i++) a[i + 1] = s[i];
        b1[0] = b2[0] = 1, s1[0] = s2[0] = 0;
        for(int i = 1; i <= n; i++)
            b1[i] = (b1[i - 1] * b) % h1,
            b2[i] = (b2[i - 1] * b) % h2;
        for(int i = 1; i <= n; i++)
            s1[i] = (s1[i - 1] * b + a[i]) % h1,
            s2[i] = (s2[i - 1] * b + a[i]) % h2;
        for(int i = 1; i <= n; i++)
        {
            long long num1l, num2l, num1r, num2r;
            if(i - 1 < len)
            {
                int tot = len + 1 - i;
                num1l = (get1(i + 1, len + 1) + (get1(1, i - 1) * b1[len - i + 1]) % h1) % h1;
                num2l = (get2(i + 1, len + 1) + (get2(1, i - 1) * b2[len - i + 1]) % h2) % h2;
                num1r = get1(n - len + 1, n);
                num2r = get2(n - len + 1, n);
            }
            else if(i - 1 > len)
            {
                num1l = get1(1, len);
                num2l = get2(1, len);
                num1r = (get1(i + 1, n) + (get1(n - len, i - 1) * b1[n - i]) % h1) % h1;
                num2r = (get2(i + 1, n) + (get2(n - len, i - 1) * b2[n - i]) % h2) % h2;
            }
            else if(i - 1 == len)
            {
                num1l = get1(1, len);
                num2l = get2(1, len);
                num1r = get1(n - len + 1, n);
                num2r = get2(n - len + 1, n);
            }
            if(num1l == num1r && num2l == num2r)
            {
                if(pos != -1 && (ans1 != num1l || ans2 != num2l))
                {
                    printf("NOT UNIQUE");
                    return 0;
                }
                pos = i;
                ans1 = num1l, ans2 = num2l;
            }
        }
        if(pos == -1)
        {
            printf("NOT POSSIBLE");
            return 0;
        }
        if(pos - 1 < len)
        {
            for(int i = 1; i < pos; i++) res += a[i];
            for(int i = pos + 1; i <= len + 1; i++) res += a[i];
        }
        else for(int i = 1; i <= len; i++) res += a[i];
        cout << res;
        return 0;
    }
    

    A Horrible Poem

    题目链接

    参考题解

    Solution

    数据范围非常不友好...首先,x 是区间 ([l,r]) 的一个循环节的条件是:

    1. x 是 r-l+1 的一个因数

    2. ([l,r]) 去掉前 x 个字符和 ([l,r]) 去掉后 x 个字符的哈希值相等,即 (H_{l+x,r}=H_{l,r-x})

    如果暴力枚举因数,根号的复杂度会超时,考虑优化:

    设最小循环节长度为 len,则原区间长度 n = len * k。将 k 的质因数依次分解,每次试除 k,得到的 k' 和 len 的乘积仍是一个循环节。利用这个性质,依次用质数 i 试除 n,若除去后仍是循环节,说明 i 是 k 的一部分。将其除去,留下的就是 len。

    Solution

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #define int long long
    using namespace std;
    
    const int N = 2333333, h1 = 1e9 + 7, h2 = 1e9 + 9, b = 131;
    int n, Q, A, B, cnt = 0, ans, tmp;
    int b1[N], b2[N], s1[N], s2[N], minp[N], p[N];
    string s;
    
    void Prim()
    {
        memset(minp, 0, sizeof(minp));
        for(int i = 2; i <= n; i++)
        {
            if(!minp[i]) p[++cnt] = i, minp[i] = i;
            for(int j = 1; j <= cnt; j++)
            {
                if(i * p[j] > n || p[j] > minp[i]) break;
                minp[i * p[j]] = p[j];
            }
        }
    }
    
    int get1(int l, int r)
    {
        return ((s1[r] - s1[l - 1] * b1[r - l + 1]) % h1 + h1) % h1;
    }
    int get2(int l, int r)
    {
        return ((s2[r] - s2[l - 1] * b2[r - l + 1]) % h2 + h2) % h2;
    }
    bool check(int l, int r, int x)
    {
        return (get1(l + x, r) == get1(l, r - x)) && (get2(l + x, r) == get2(l, r - x));
    }
    
    signed main()
    {
        cin >> n >> s;
        Prim();
        b1[0] = b2[0] = 1;
        for(int i = 1; i <= n; i++) b1[i] = (b1[i - 1] * b) % h1;
        for(int i = 1; i <= n; i++) b2[i] = (b2[i - 1] * b) % h2;
        s1[0] = s2[0] = 0;
        for(int i = 1; i <= n; i++) s1[i] = (s1[i - 1] * b + s[i - 1]) % h1;
        for(int i = 1; i <= n; i++) s2[i] = (s2[i - 1] * b + s[i - 1]) % h2;
        scanf("%lld", &Q);
        while(Q--)
        {
            scanf("%lld%lld", &A, &B);
            ans = tmp = B - A + 1;
            while(tmp != 1)
            {
                int t = minp[tmp];
                while(tmp % t == 0 && check(A, B, ans / t)) ans /= t, tmp /= t;
                while(tmp % t == 0) tmp /= t;
            }
            printf("%lld
    ", ans);
        }
        return 0;
    }
    
  • 相关阅读:
    Centos Another app is currently holding the yum lock
    Centos 重置密码
    Effective c#学习笔记(1)
    浅谈计算机编码
    mongodb java spring data
    VS2013 好用的插件
    xml存储bug
    VS 2008 生成操作中各个选项的差别
    程序权限控制
    给钛度产品的一些建议(Note)
  • 原文地址:https://www.cnblogs.com/Andy-park/p/13569307.html
Copyright © 2020-2023  润新知