P5070 [Ynoi2015] 即便看不到未来
[Ynoi2015] 即便看不到未来
在太阳西斜的这个世界里,置身天上之森。等这场战争结束之后,不归之人与望眼欲穿的众人, 人人本着正义之名,长存不灭的过去、逐渐消逝的未来。我回来了,纵使日薄西山,即便看不到未来,此时此刻的光辉,盼君勿忘。
————世界上最幸福的女孩 珂朵莉
思路
扫描线。
发现 \(k\leq 10\),也就是意味着 \(k\) 就是一个大常数,那我们就可以对每个 \(k\) 分开考虑。
我们考虑扫描线经典套路就是先把问题 \(l,r\) 离线并看成一个点 \((l,r)\),枚举每个枚举右端点并把左端点分段,这样的贡献可以当成矩形,原问题变成二维数点。
这道题也是同理,我们枚举右端点 \(r\) ,并且钦定当前位置就是我扫描线矩阵 \(x\) 轴的右端点,设 \([lv,rv]\) 是现在左端点到右端点中包括右端点的极长值域连续段的值域区间,那么当前序列上的区间就不能包括 \(lv-1,rv+1\) 这样的左端点的区间应该在 \((\max(pre_{lv-1},pre_{rv+1}),\min(pre_{lv},pre_{rv})]\) 之间,(其中 \(pre_v\) 表示值 \(v\) 上一次出现的位置,这个可以 \(\mathcal O(1)\) 维护)。
考虑每次 \(k\leftarrow k+1\) 的过程,实际上就是把 \([lv,rv]\) 变成 \([lv-1,rv]\) 或者 \([lv,rv+1]\) 的过程,而扩展前者还是后者则取决于 \(pre_{lv-1}\) 和 \(pre_{rv+1}\) 的大小关系,我们必定会拓展到 \(pre_{lv-1},pre_{rv+1}\) 更大的位置,因为如果反之那么就会同时扩展两个数,不满足 \(k\leftarrow k+1\) 的条件。
最后我们考虑去重的问题,这并不是简单的矩形面积并的问题,因为一个区间 \(l,r\) 内可能包括很多长度相等的值域连续段,其实去重问题也很好解决,我们只需要关心左端点区间是不是严格 \(\leq pre_{a_r}\) 即可,因为前面的矩形一定会在右端点枚举到 \(pre_{a_r}\) 的时候加入扫描线,此时重复加入必然带来过多的贡献。
\(y\) 轴上的矩形边的处理就可以随意点了,因为 \(y\) 轴上左端点必然是 \(r\) ,而右端点则是 \(\min(nxt_{lv-1},nxt_{rv+1})\),可以用 vector
+ 指针达到 \(\mathcal O(1)\) 处理
最后做 \(k\) 次二维数点即可,问题转化为区间+单点求值,建议用常数小的树状数组。
时间复杂度 \(\mathcal O((n+m)k\log n)\),卡卡就能过。
code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 1e6 + 10;
const int K = 10;
const int INF = 0x3f3f3f3f;
typedef pair <int, int> pii;
inline int read ()
{
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar (); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, m;
int a[N];
struct Query {
int x, y, id;
Query () {}
Query (int _x, int _y, int _id) { x = _x, y = _y, id = _id; }
bool operator < (const Query &A) const {
return x < A.x;
}
} q[N];
struct scanline {
int x, l, r, op;
scanline () {}
scanline (int _x, int _l, int _r, int _op) { x = _x, l = _l; r = _r; op = _op; }
bool operator < (const scanline &A) const {
return x < A.x;
}
} line[K + 2][N << 1]; int lcnt[K + 2];
struct BIT {
#define lowbit(x) x & (-x)
int tree[N];
void init () { memset (tree, 0, sizeof (tree)); }
void add (int x, int val) { for ( ; x <= n; x += lowbit(x)) tree[x] += val; }
int query (int x) { int res = 0; for ( ; x > 0; x -= lowbit (x)) res += tree[x]; return res; }
} T[K + 2];
bool vis[N];
int pos[N];
vector <int> vec[N];
int point[N];
int ans[N][K + 2];
signed main()
{
n = read (), m = read (); int mx = 0;
for (int i = 1; i <= n; i++) a[i] = read (), vec[a[i]].pb (i), mx = max (mx, a[i]), vis[a[i]] = true;
for (int i = 0; i <= mx + 1; i++) vec[i].pb (n + 1);
for (int i = 1; i <= m; i++) q[i].x = read (), q[i].y = read (), q[i].id = i;
sort (q + 1, q + m + 1);
for (int i = 1; i <= n; i++)
{
int x = a[i], y = a[i];
int l1, r1, r2, lst = pos[a[i]];
pos[a[i]] = i;
for (int j = 1; j <= K; j++)
{
l1 = max (pos[x - 1], pos[y + 1]); r1 = min (pos[x], pos[y]);
if (r1 <= lst && lst) break; // 去重
if (l1 > r1)
{
while (pos[x - 1] > r1) x--, j++;
while (pos[y + 1] > r1) y++, j++;
if (j > K) break;
l1 = max (pos[x - 1], pos[y + 1]);
} // 扩展一位的同时扩入了多个,这也说明当前枚举的长度 k 在右端点为 i 时不存在
r2 = min (vec[x - 1][point[x - 1]], vec[y + 1][point[y + 1]]) - 1;
// vector O(1) 处理nxt
line[j][++lcnt[j]] = scanline (max (l1 + 1, lst + 1), i, r2, 1);
line[j][++lcnt[j]] = scanline (r1 + 1, i, r2, -1);
if (!pos[x - 1] && !pos[y + 1]) break;
else pos[x - 1] > pos[y + 1] ? x-- : y++;
}
point[a[i]]++;
}
for (int k = 1; k <= K; k++)
{
sort (line[k] + 1, line[k] + lcnt[k] + 1);
int now = 1;
for (int i = 1; i <= m; i++)
{
while (now <= lcnt[k] && line[k][now].x <= q[i].x)
T[k].add(line[k][now].l, 1 * line[k][now].op),
T[k].add(line[k][now].r + 1, -1 * line[k][now].op),
now++;
(ans[q[i].id][k] += T[k].query(q[i].y)) %= 10;
}
}
for (int i = 1; i <= m; i++, printf("\n"))
for (int k = 1; k <= K; k++)
printf ("%d", ans[i][k]);
return 0;
}