感谢 \(\mathcal{AutumnKite}\) 神犇提供的思路!
\[\texttt{Description} \]CF1677E Tokitsukaze and Beautiful Subsegments
\[\texttt{Solution} \]一个区间 \(l \sim r\) 是美丽的,当且仅当存在两个数 \(i, j\) 满足 \(l \leq i < j \leq r\) 且 \(a_i \cdot a_j = \max\limits^{k\leq r}_{k=l}a_k\)。
看到 \(\max\limits^{k\leq r}_{k=l}a_k\),我们就能想到用一个单调栈来解决。
我们可以先用单调栈预处理处对于每一个 \(a_i\),它后面第一个比它大的数的下标 \(R_i\) 和它前面第一个比它大的数的下标 \(L_i\)。
所以对于所有的区间 \(l \sim r\) 满足 \(L_i < l \leq r < R_i\),都有 \(\max\limits^{k\leq r}_{k=l}a_k=a_i\)。
于是我们就能够直接得到一个区间的最大值。
接着我们先考虑如何求解整个数列的美丽区间个数。
可以先对于每个 \(a_i\) 暴力枚举,枚举区间的右端点 \(r\ (i\leq r < R_i)\),确定左端点的范围,也可以枚举区间左端点 \(l \ (L_i < l \leq i)\),确定右端点的范围。
我们这里以枚举右端点为例。
假设我们现在有一个区间:
\(1\ \ 3\ \ 4\ \ 2\ \ 12\ \ 8\ \ 6\)。
枚举到的 \(a_i = 12\)。
我们设区间左端点的范围为 \(L \sim R\),明显 \(L = L_i + 1\)。
首先,我们初始化 \(R = L_i\),也就是没有合法的区间。
接着,我们枚举 \(a_i\) 的因数,设两个因数分别为 \(x, y\ \ (x \ne y)\),数的位置为 \(pos_x\) 和 \(pos_y\)(\(pos\) 数组可以预处理,因为这是一个 \(1 \sim n\) 的排列),判断是否 \(L_i < pos_x, pos_y \leq i\),即在范围内,然后更新 \(R = \min(pos_x, pos_y)\)。
感性理解一下,例如在这里,我们可以枚举到 \(12\) 的因数有 \(3\) 和 \(4\)。
所以更新 \(R = \min(pos_3, pos_4) = 2\),即只需左端点在 \(1 \sim 2\) 中,包含着 \(3\) 和 \(4\),那么就有 \(3 \times 4 = 12\),即这个区间是美丽的。
这样总时间复杂度为 \(\mathcal{O(\Sigma^{i\leq n}_{i=1}\sqrt a_i)}\)。
然后我们可以对于每一个枚举到的右端点,算出与它的乘积为 \(a_i\) 的那个数的位置,判断是否在范围内,如果在,则更新 \(R = \max(R, pos)\),这里要注意,可能 \(pos > i\),这是需要在更新 \(R = \min(R, i)\)。
例如枚举到 \(6\) 时,算出 \(\dfrac{12}{6}=2, pos_2 = 4\),所以更新 \(R = 4\),即右端点在 \(6\) 之后只需要左端点中包含 \(2\),就有 \(2 \times 6 = 12\),即这个区间是美丽的。
对于每一个这样的三元组 \((L,R,r)\) 表示右端点为 \(r\) 时左端点为 \(L \sim R\) 时区间时美丽的,我们都可以用一个 vector 把它记录下来。
具体地,我们可以这样操作:
vector < pair <int, int> > vec[MAXN]; // Insert operation. vec[r].push_back(make_pair(L, R));
如果枚举左端点,三元组 \((L, R, l)\) 表示左端点为 \(l\) 时右端点为 \(L \sim R\) 时区间是美丽的,同理。
如果全局查询,我们只需要把每一个三元组的 \(R - L + 1\) 给加起来就是答案。
可是现在是区间查询。
所以我们可以参考扫描线的思想。
首先把询问离线,按照右端点从小到大排序,用 \(i\) 指针从 \(1\ sim n\) 枚举,每次把右端点为 \(i\) 的所有三元组给加到一个线段树中,就是把区间 \(L \sim R\) 加上 \(1\),然后遇到对应的询问 \((l, r)\) 时,查询线段树上 \(l \sim r\) 的和就行了。
如果时枚举左端点,就按照左端点从大到小排序,用 \(i\) 指针逆序扫,其余同理。
这样我们可以完成区间查询了,但是时间复杂度却是可怜的 \(\mathcal{O(n^2)}\)。
我们会发现对于一个数 \(a_i\),枚举左端点和右端点都可以,根据正常人的思维,哪一个短就枚举哪一个。
结果我们发现这过了。
其实这个是启发式分裂的思想,这样能够保证枚举的数量不超过 \(n \log n\) 个。
所以加上线段树查询的时间复杂度,总时间复杂度为 \(\mathcal{O(n \log^2 n + q \log n + \Sigma^{i\le n}_{i = 1}\sqrt a_i)}\)。
\[\texttt{Code} \]最后不要忘记开 long long。
并且这道题有一些神奇,不用快读快输过不去。
// Thanks for solution given from AutumnKite! Orz %%% #include <bits/stdc++.h> using namespace std; #define int long long static char buf[100010], *p1 = buf, *p2 = buf; #define gc p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100010, stdin), p1 == p2)? EOF: *p1++ inline int read() { int res = 0, w = 0; char c = gc; while (!isdigit(c)) w |= c == '-', c = gc; while (isdigit(c)) res = (res << 1) + (res << 3) + (c ^ 48), c = gc; return (w? -res : res); } inline void write(int x) { static int sta[50], top = 0; if (x < 0) putchar('-'), x = -x; do { sta[ top++ ] = x % 10, x /= 10; } while (x); while (top) putchar(sta[ --top ] + 48); putchar('\n'); } #define ls (p << 1) #define rs (p << 1 | 1) const int MAXN = 2e5 + 10; int n, Q, a[MAXN], pos[MAXN], L[MAXN], R[MAXN]; int sta[MAXN], top, Qnum; vector < pair <int, int> > vec1[MAXN], vec2[MAXN]; struct Query { int l, r, id; }q[ (int)(1e6 + 10) ]; int Ans[ (int)(1e6 + 10) ]; inline bool Comp1(Query a, Query b) { return a.r < b.r; } inline bool Comp2(Query a, Query b) { return a.l > b.l; } struct Node { int len, data, add; Node() = default; Node(const int Len, const int Data, const int Add) : len(Len), data(Data), add(Add) {} }t[ MAXN << 2 ]; inline void build(int p, int l, int r) { t[p] = Node(r - l + 1, 0, 0); if (l == r) return ; int mid = l + r >> 1; build(ls, l, mid); build(rs, mid + 1, r); } inline void pushdown(int p) { t[ls] = Node(t[ls].len, t[ls].data + t[ls].len * t[p].add, t[p].add + t[ls].add); t[rs] = Node(t[rs].len, t[rs].data + t[rs].len * t[p].add, t[p].add + t[rs].add); t[p].add = 0; } inline void update(int p, int l, int r, int x, int y, int k) { if (l >= x && r <= y) { t[p] = Node(t[p].len, t[p].data + t[p].len * k, t[p].add + k); return ; } pushdown(p); int mid = l + r >> 1; if (x <= mid) update(ls, l, mid, x, y, k); if (mid < y) update(rs, mid + 1, r, x, y, k); t[p].data = t[ls].data + t[rs].data; } inline int query(int p, int l, int r, int x, int y) { if (l >= x && r <= y) return t[p].data; int ans = 0, mid = l + r >> 1; pushdown(p); if (x <= mid) ans += query(ls, l, mid, x, y); if (mid < y) ans += query(rs, mid + 1, r, x, y); return ans; } signed main() { n = read(); Q = read(); for (int i = 1; i <= n; i++) { a[i] = read(); pos[ a[i] ] = i; } top = 0; for (int i = 1; i <= n; i++) { while (top > 0 && a[ sta[top] ] < a[i]) top--; L[i] = sta[top]; sta[ ++top ] = i; } top = 0; sta[0] = n + 1; for (int i = n; i >= 1; i--) { while (top > 0 && a[ sta[top] ] < a[i]) top--; R[i] = sta[top]; sta[ ++top ] = i; } for (int i = 1; i <= Q; i++) { q[i].l = read(); q[i].r = read(); q[i].id = i; } for (int i = 1; i <= n; i++) { int left, right; if (R[i] - i <= i - L[i]) { right = L[i]; for (int j = 1; j * j <= a[i]; j++) { if (a[i] % j != 0) continue; int fx = pos[j], fy = pos[ a[i] / j ]; if (fx == fy) continue; if (fx > fy) swap(fx, fy); if (fx > L[i] && fy <= i) right = max(right, fx); } for (int j = i; j < R[i]; j++) { if (a[i] % a[j] == 0) { int num = a[i] / a[j]; if (pos[num] == j) continue; if (pos[num] > L[i] && pos[num] < j) { right = max(right, pos[num]); right = min(right, i); } } if (L[i] < right) vec1[j].push_back(make_pair(L[i] + 1, right)); } } else { left = R[i]; for (int j = 1; j * j <= a[i]; j++) { if (a[i] % j != 0) continue; int fx = pos[j], fy = pos[ a[i] / j ]; if (fx == fy) continue; if (fx > fy) swap(fx, fy); if (fy < R[i] && fx >= i) left = min(left, fy); } for (int j = i; j > L[i]; j--) { if (a[i] % a[j] == 0) { int num = a[i] / a[j]; if (pos[num] == j) continue; if (pos[num] > j && pos[num] < R[i]) { left = min(left, pos[num]); left = max(left, i); } } if (left < R[i]) vec2[j].push_back(make_pair(left, R[i] - 1)); } } } sort(q + 1, q + Q + 1, Comp1); build(1, 1, n); Qnum = 1; for (int i = 1; i <= n; i++) { for (auto v : vec1[i]) { update(1, 1, n, v.first, v.second, 1); } for (; q[Qnum].r == i && Qnum <= Q; Qnum++) { Ans[ q[Qnum].id ] += query(1, 1, n, q[Qnum].l, q[Qnum].r); } } sort(q + 1, q + Q + 1, Comp2); build(1, 1, n); Qnum = 1; for (int i = n; i >= 1; i--) { for (auto v : vec2[i]) { update(1, 1, n, v.first, v.second, 1); } for (; q[Qnum].l == i && Qnum <= Q; Qnum++) { Ans[ q[Qnum].id ] += query(1, 1, n, q[Qnum].l, q[Qnum].r); } } for (int i = 1; i <= Q; i++) write(Ans[i]); return 0; }\[\texttt{Thanks for watching!} \]