LG 题解 P5068 [Ynoi2015] 我回来了

题目无关

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

珂朵莉,欢迎回来。

写在前面

感谢 Froggy 的思路。

前置芝士

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

Description

题目传送

Solution

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

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

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

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

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

显然,

\[t_{d,i} = \min_{d \times i - d + 1 \le j \le d \times min(d \times 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\n", Bit::Query(q[i].r) - Bit::Query(q[i].l - 1));
        }
    }
    return 0;
}
posted @ 2021-08-07 17:39  Suzt_ilymtics  阅读(94)  评论(1编辑  收藏  举报