P3987 我永远喜欢珂朵莉~ 题解

· · 题解

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

注:感谢wxw大佬提供的hack数据,这题数据实在很奇葩。

解题思路:

这道题需要维护区间除法,普通的线段树肯定是做不到的,可是像wxw大佬那样的卡常又不会。

但!是!这可是一道信仰题,怎么可以轻易放弃!

于是我们发挥瞎搞精神,线段树能做到的得做,不能做到的也得给我做(线段树:我招谁惹谁了我)。

既然区间除法不行,那单点除法肯定是行的,现在问题就转化为了在一个区间内如何快速找到 x 的倍数。

暴力肯定不可行,那么我们就可以预处理一下,开 n 个动态数组 e

每次遇到除法,就在 $e_x$ 里头找第一个大于等于 $l$ 和最后一个小于等于 $r$ 的数字,在这个区间内暴力单点修改。 但是有个问题,修改完之后有可能就不是 $x$ 的倍数了,所以这里要引进一个玄学优化。 每次记录操作 $1$ 除以 $x$ 的次数,如果次数很多,每操作一次就将不是 $x$ 的倍数的删除掉,这样以后暴力修改能快点。 事实上 $500000$ 大概除个 $19$ 次 $2$ 就差不多到 $1$ 了,时间复杂度上完全能接受。 ## AC代码: ```cpp #include<iostream> #include<vector> #include<cstdio> using namespace std; #define int long long namespace in { //快读模板 #ifdef faster char buf[1 << 21], * p1 = buf, * p2 = buf; inline int getc() { return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++; } #else inline int getc() { return getchar(); } #endif template <typename T>inline void read(T& t) { t = 0; int f = 0; char ch = getc(); while (!isdigit(ch)) { if (ch == '-')f = 1; ch = getc(); } while (isdigit(ch)) { t = t * 10 + ch - 48; ch = getc(); }if (f)t = -t; } template <typename T, typename... Args> inline void read(T& t, Args&... args) { read(t); read(args...); } } vector<int> e[500005]; int n, m, a[500005], t[500005], tree[2000005]; struct node { int op, u, v, w; }s[500005]; inline int find1(int now, int x)//二分 { int l = 0, r = e[x].size() - 1, ans = -1, mid; while (l <= r) { mid = (l + r) >> 1; if (e[x][mid] >= now) { ans = mid; r = mid - 1; } else l = mid + 1; } return ans; } inline int find2(int now, int x) { int l = 0, r = e[x].size() - 1, ans = -1, mid; while (l <= r) { mid = (l + r) >> 1; if (e[x][mid] <= now) { ans = mid; l = mid + 1; } else r = mid - 1; } return ans; } inline void cf(int now, int x)//这里是预处理,将符合条件的数放进动态数组里 { for (register int i = 1; i * i <= now; ++i) { if (now % i == 0) { e[i].push_back(x); if (i * i != now) e[now / i].push_back(x); } } } inline void build(int now, int l, int r)//线段树基本操作 { if (l == r) { tree[now] = a[l]; return; } int mid = (l + r) / 2; build(now * 2, l, mid); build(now * 2 + 1, mid + 1, r); tree[now] = tree[now * 2] + tree[now * 2 + 1]; } inline void update(int now, int l, int r, int k, int x) { if (l > k || r < k) return; if (l == k && r == k) { tree[now] /= x; return; } int mid = (l + r) / 2; update(now * 2, l, mid, k, x); update(now * 2 + 1, mid + 1, r, k, x); tree[now] = tree[now * 2] + tree[now * 2 + 1]; } inline int query(int now, int l, int r, int x, int y) { if (l > y || r < x) return 0; if (x <= l && r <= y) return tree[now]; int mid = (l + r) / 2, res; res = query(now * 2, l, mid, x, y) + query(now * 2 + 1, mid + 1, r, x, y); return res; } inline void divide(int l, int r, int x) { int head = find1(l, x), tail = find2(r, x); if (head == -1) return; for (register int i = head; i <= tail; ++i) { int now = e[x][i]; if (a[now] % x == 0) a[now] /= x, update(1, 1, n, now, x); else if (t[x] >= 10000)//这里就是玄学优化 e[x].erase(e[x].begin() + i), --i,--tail; } } signed main() { in::read(n, m); for (register int i = 1; i <= n; ++i) { in::read(a[i]); cf(a[i], i); } build(1, 1, n); for (register int i = 1; i <= m; ++i) { in::read(s[i].op); if (s[i].op == 1) { in::read(s[i].u, s[i].v, s[i].w); ++t[s[i].w]; } else in::read(s[i].u, s[i].v); } for (register int i = 1; i <= m; ++i) { if (s[i].op == 1) { if (s[i].w == 1) continue; divide(s[i].u, s[i].v, s[i].w); --t[s[i].w]; } else printf("%lld\n", query(1, 1, n, s[i].u, s[i].v)); } return 0; } ```