• LG 题解 P5068 [Ynoi2015] 我回来了


    题目无关

    在太阳西斜的这个世界里,置身天上之森。 等这场战争结束后,不归之人与望眼欲穿的人们,人人本着正义之名。 长存不灭的过去,逐渐消逝的未来。 我回来了, 纵使日薄西山,即便看不到未来, 此时此刻的光辉,盼君勿忘。 ——世上最幸福的女孩

    珂朵莉,欢迎回来。

    写在前面

    感谢 Froggy 的思路。

    前置芝士

    • 树状数组
    • 倍增
    • 二维偏序

    Description

    题目传送

    Solution

    题目要求期望值,其实就是求 (d in [L,R]) 所有解的和。

    容易发现对于一个伤害值 (d),我们要依次判断 ([1,d],[d+1,2d],...,[d imes left lfloor frac{n}{d} ight floor + 1,n]) 中是否存在一个随从。

    考虑把所有操作离线下来。然后分别求出每个区间最早存在随从时是在第几次操作。

    设伤害值为 (d) 的第 (i) 个区间在 (t_{d,i}) 个操作时最早有随从了。

    (a_i) 表示血量为 (i) 的随从最早出现的时间,如果从未出现过应设为 (m+1)

    显然,

    [t_{d,i} = min_{d imes i - d + 1 le j le d imes min(d imes i, n)}{a_j} ]

    可以用 ST 表 (O(n log n)) 预处理,实现 (O(1)) 查询。

    然后设 (g_{d,i}) 表示伤害值为 (d),至少发生 (i) 次的最早操作时间。显然,

    [g_{d,i} = max {g_{d,i-1},t_{d,i}} ]

    我们用 vector 存下每个操作时间会有那些伤害发生。即把每个 (g_{d,i}) 存进去。

    发现伤害值为 (d) 的第 (i) 次伤害会对 (g_{d,i}) 后的所有时间有贡献,是一个典型的二维偏序问题,可以用树状数组维护。

    (g) 数组的数量是一个调和级数,加上在树状数组上修改,复杂度为 (O(n log^2 n))

    每次询问复杂度 (log n),共 (O(m log n))

    总复杂度为 (O(n log^2 n + m log n))

    代码还非常好写。

    Code

    /*
    Work by: Suzt_ilymics
    Problem: 不知名屑题
    Knowledge: 垃圾算法
    Time: O(能过)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #define LL long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 2e6+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    struct node { int l, r; }q[MAXN];
    int n, m;
    int a[MAXN]; // a[i] 表示 i 出现的最早时间 
    int lg[MAXN], f[MAXN][22];
    vector<int> v[MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    namespace Bit {
        int sum[MAXN];
        int lowbit(int x) { return x & -x; }
        void Modify(int x, int val) { for( ; x <= n; x += lowbit(x)) sum[x] += val; }
        int Query(int x) { int res = 0; for( ; x; x -= lowbit(x)) res += sum[x]; return res; }
    }
    
    int GetMin(int l, int r) {
        int k = lg[r - l + 1];
        return min(f[l][k], f[r - (1 << k) + 1][k]);
    }
    
    int main()
    {
        n = read(), m = read();
        for(int i = 1; i <= n; ++i) a[i] = m + 1;
        for(int i = 1, opt, x, l, r; i <= m; ++i) {
            opt = read(); q[i].l = q[i].r = -1;
            if(opt == 1) x = read(), a[x] = min(a[x], i);
            else q[i].l = read(), q[i].r = read();
        }
        for(int i = 2; i <= n; ++i) lg[i] = lg[i >> 1] + 1;
        for(int i = 1; i <= n; ++i) f[i][0] = a[i];
        for(int i = 1; i <= 18; ++i) {
            for(int j = 1; j + (1 << i) - 1 <= n; ++j) {
                f[j][i] = min(f[j][i - 1], f[j + (1 << i - 1)][i - 1]);
                // 表示 [j, j + 2^i - 1] 这段值域出现的最早时间 
            }
        }
        for(int i = 1; i <= n; ++i) {
            Bit::Modify(i, 1);
            for(int j = 1, lst = 0; j <= n; j += i) {
                lst = max(lst, GetMin(j, min(j + i - 1, n)));
                v[lst].push_back(i); // 把每个 g_{d,i} 放进出现时的操作次数对应的 vector 数组里 
            }
        }
        for(int i = 1; i <= m; ++i) {
            for(int j = 0, M = v[i].size(); j < M; ++j) Bit::Modify(v[i][j], 1);
            if(q[i].l != -1) {
                printf("%d
    ", Bit::Query(q[i].r) - Bit::Query(q[i].l - 1));
            }
        }
        return 0;
    }
    
  • 相关阅读:
    《实战Java高并发程序设计》读书笔记一
    《实战Java高并发程序设计》读书笔记二
    SprintBoot学习(三)
    SprintBoot学习(二)
    SprintBoot学习(一)
    jQuery学习(三)
    jQuery学习(二)
    jQuery学习(一)
    利用activeX控件在网页里自动登录WIN2003远程桌面并实时控制
    上传读取Excel文件数据
  • 原文地址:https://www.cnblogs.com/Silymtics/p/15112550.html
Copyright © 2020-2023  润新知