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\)。
显然,
可以用 ST 表 \(O(n \log n)\) 预处理,实现 \(O(1)\) 查询。
然后设 \(g_{d,i}\) 表示伤害值为 \(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;
}