Dyd's Blog

He who has a strong enough why can bear almost any how.

luoguP5072 [Ynoi2015] 盼君勿忘

我是傻子

盼君勿忘

给定一个长度为 $n \le 10^5$ 的序列 $a_i \le 10^5$ , $m \le 10^5$ 次询问,给定 $l, r, p(1 \le l \le r \le n, p \le 10^9)$ ,求区间 $[l, r]$ 的所有子序列分别去重后的和 $\mod p$

思路

看出题人想 $\sqrt{n}$ 算法

先拆贡献,对于区间 $[l, r]$ ,若数字 $x$ 在区间内出现了 $cnt$ 次,那么它的贡献就是 $x * (2^{r - l + 1} - 2^{r - l + 1 - cnt})$ (就是所有子序列 - 不存在 $x$ 的子序列)

由于要维护动态 $cnt$ ,马上想到莫队(完了,我不擅长莫队 但仔细一想我好像没有擅长的欸 ),直接套莫队板子就可以 $O(n \sqrt{n})$ 维护 $cnt$

但每次询问我们都要把每个 $x$ 算一个贡献,最坏要把整个区间扫完,就又挂了

这里有不同的解法, wfy巨佬说可以平衡规划,把整个序列出现次数 $\ge \sqrt{n}$ 的数字记下来,然后在莫队中把出现次数相同的数都加到一起(反正它们都要乘 $(2^{r - l + 1} - 2^{r - l + 1 - cnt})$ ),每次计算时不枚举 $x$ 而枚举 $cnt$ ,此时 $cnt$ 必定 $\le \sqrt{n}$ ,因为大于的我们都调记录了,时间就是 $O(n \sqrt{n})$

但蒟蒻我比较弱,只能考虑暴力上数据结构,还是把出现次数相同的数都加到一起,然后枚举 $cnt$ ,但发现很多 $cnt$ 里一个数字都没有,于是考虑用一个链表存储有意义的 $cnt$ (不能用 STL 因为它删除是 $O(n)$ 的,建议手写双向链表),这样就不会枚举到无用的 $cnt$ ,考虑到不同的 $cnt$ 最多只有 $\sqrt{n}$ 级(因为 $cnt$ 代表是数的个数,而 $1 + 2 + 3 + … + x = \frac{x (x - 1)}{2} \le n$ ),所以时间就也是 $O(n \sqrt{n})$

最后一个问题是 $2^{r - l + 1 - cnt}$ ,快速幂计算显然每次 $O(\log n)$ ,不可接受( lxl 的题还想 $\log$ 算法?),考虑光速幂,每次询问 $\sqrt{n}$ 预处理, $O(1)$ 解决每次计算(这里注意,由于指数最大是 $n$ ,所以只用处理到 $\sqrt{n}$ ,而不是 $\sqrt{p}$ ,且也不用 $\mod \varphi(p)$ )

总时间 $O(n \sqrt{n})$

代码

我光速幂打挂了三次……,一共就两个函数……(记得 $B \ge \sqrt{n}$ )

我的打法常数略大,但反正可过

如果你被卡常了,这里分享一些卡常技巧:

  1. 快读/快输
  2. 别万能头
  3. inline
  4. 把答案用 __int128_ / long double 存,中间不取模,最后在统一取模(不会爆)
  5. 光速幂用进制拆分
  6. 调块长(但一点要大于 $\sqrt{n}$ )

反正我没被卡,就一个都不想用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <bits/stdc++.h>
#define get(x) ((x - 1) / B + 1)
using LL = long long; //从wfy巨佬那里学来的打法
const int N = 1e5 + 5, B = 316;
int n, m;
int a[N];
struct Ques{ int id, l, r, p; } q[N];
int ans[N];
namespace MD
{
struct List
{
struct Node
{
int l, r, bid; //bid,指回id,就是cnt
LL sum;
} nd[N];
int rb[N], top, tot, id[N]; //rb垃圾桶,回收空间用
int newnd(){ return top ? rb[top--] : ++tot; }
void ins(int x, LL d) //插入出现次数为x的数d
{
if (!x) return ;
if (!id[x])
{
id[x] = newnd();
nd[id[x]] = {0, nd[0].r, x, 0};
nd[nd[0].r].l = id[x];
nd[0].r = id[x];
}
nd[id[x]].sum += d;
}
void erase(int x, LL d)
{
if (!x || !id[x]) return ;
if (!(nd[id[x]].sum -= d))
{
nd[nd[id[x]].l].r = nd[id[x]].r, nd[nd[id[x]].r].l = nd[id[x]].l;
rb[++top] = id[x];
id[x] = 0;
}
}
} lis;
int cnt[N], il = 1, ir = 0;
void upd(int x, int y){ lis.erase(cnt[x], x), lis.ins(cnt[x] += y, x); }
void calc(int l, int r)
{
for (; il > l; upd(a[--il], 1));
for (; ir < r; upd(a[++ir], 1));
for (; il < l; upd(a[il++], -1));
for (; ir > r; upd(a[ir--], -1));
}
}
namespace LPow
{
int lp[N][2], p;
void prev(int x, LL _p)
{
p = _p, lp[0][1] = lp[0][0] = 1;
for (int i = 1; i <= B + 5; ++i) lp[i][0] = LL(lp[i - 1][0]) * x % p;
for (int i = 1; i <= B + 5; ++i) lp[i][1] = LL(lp[i - 1][1]) * lp[B][0] % p;
}
int lpow(int y){ return LL(lp[y % B][0]) * lp[y / B][1] % p; }
}
using MD::lis;
using MD::calc;
using LPow::lpow;
using LPow::prev;
int main()
{
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= m; ++i) scanf("%d %d %d", &q[i].l, &q[i].r, &q[i].p), q[i].id = i;
std::sort(q + 1, q + 1 + m, [&](Ques a, Ques b){ return get(a.l) == get(b.l) ? a.r < b. r : get(a.l) < get(b.l);});
for (int i = 1; i <= m; ++i)
{
calc(q[i].l, q[i].r), prev(2, q[i].p);
int &as = ans[q[i].id];
for (int j = lis.nd[0].r; j; j = lis.nd[j].r)
{
MD::List::Node &nd = MD::lis.nd[j];
as = (as + nd.sum % q[i].p * LL(lpow(q[i].r - q[i].l + 1) - lpow(q[i].r - q[i].l + 1 - nd.bid) + q[i].p) % q[i].p) % q[i].p;
}
}
for (int i = 1; i <= m; ++i) printf("%d\n", ans[i]);
return 0;
}