前言
找原题未遂了()
\(\rm{HD0X}\) 大佬讲了没听懂啊
思路
无敌了, 看起来似乎很困难, 不知道补不补的掉
首先发现不好处理成一种简单的问题, 肯定是想有哪些方法可以处理这种问题
\(\rm{TJ}\) 的不太看得懂
- 你可以树状数组维护区间和, 每次对于一个环暴力修改 \(\mathcal{O} (sz \log n)\), 然后查询 \(\mathcal{O} (\log n)\)
- 你也可以对一个环标记其旋转次数, 这样子做修改是 \(\mathcal{O} (1)\) 的, 查询比较复杂:
首先我们对于每个环计算其对区间的贡献, 具体的, 二分查找这个环的贡献区间, 然后常规计算
那么这样子查询是 \(\mathcal{O} (m \log sz)\)
现在我们已经有了这两种做法, 你发现这两种做法完全不同阶, 第一种做法 \(sz\) 影响最大, 第二种做法 \(m\) 影响最大, 考虑根号分治
所以设阈值 \(B\) , 假设低于阈值使用方法 \(1\) , 否则使用方法 \(2\) , 因为 \(sz\) 足够大时, 方法 \(2\) 的查询复杂度将被压缩
你发现现在的总时间复杂度为 \(\mathcal{O} (B \log n + \frac{n}{B} \log n)\) (假设 \(n, m, q\) 同阶)
令 \(B = \sqrt{n}\) , 时间复杂度达到最优, 为 \(\mathcal{O} (n \sqrt{n \log^2 n})\)
这什么神经复杂度, 算了一下达到了 \(9 \times 10^8\) , 肯定是搞不过去的
啊, 过啦
考虑优化, 但是不用也能过, 参考 题解
实现
框架
写的清楚一点, 这是码力题 :
首先处理每一个环的大小 (根据阈值分开)
对于每次修改, 小环直接暴力旋转, 大环打个标记
对于每次查询, 小环直接树状数组启动, 对于大环单独计算贡献
代码
参考 \(\rm{KLR}\) 大佬的代码
#include "iostream"
#include "algorithm"using namespace std;#define int long long
#define lowbit(x) x & -x
//#define getchar getchar_unlockedtemplate <class T>
void read(T &x) {x = 0; char ch = getchar();while (ch < '0' or ch > '9') ch = getchar();while (ch >= '0' and ch <= '9') x = x * 10 + ch - 48, ch = getchar();
}void print(int x) {if (x > 9) print(x / 10);putchar(x % 10 + '0');
}constexpr int N = 1.5e5 + 10, B = 666;int n, m, Q;
int a[N], tg[N];
basic_string<int> c[N], pre[N], ex;class Binary_Indexed_Tree {
protected:int tr[N];public: void update(int x, int k) { for (; x <= n; x += lowbit(x)) tr[x] += k; }int query(int x) {int ret = 0;for (; x; x -= lowbit(x)) ret += tr[x];return ret; }int query(int l, int r) { return query(r) - query(l - 1); }} bit;void init() {read(n), read(m), read(Q);for (int i = 1, x; i <= n; ++i) read(x), c[x].push_back(i);for (int i = 1; i <= n; ++i) read(a[i]);for (int i = 1; i <= m; ++i) {if (c[i].size() <= B) for (int x : c[i]) bit.update(x, a[x]);else {ex.push_back(i);int tmp = 0;for (int x : c[i]) pre[i].push_back(tmp += a[x]);}}
}int deal(int x, int l, int r) {l = lower_bound(c[x].begin(), c[x].end(), l) - c[x].begin(), r = upper_bound(c[x].begin(), c[x].end(), r) - c[x].begin() - 1;if (l >= (int)c[x].size() or r < l) return 0;l = (l + c[x].size() - tg[x]) % c[x].size(), r = (r + c[x].size() - tg[x]) % c[x].size();if (l > r) return *pre[x].rbegin() - (pre[x][l - 1] - pre[x][r]);return pre[x][r] - (l ? pre[x][l - 1] : 0);
}void swp(int x) {if (c[x].empty()) return;for (int i : c[x]) bit.update(i, -a[i]);bit.update(c[x][0], a[c[x][c[x].size() - 1]]);for (int i = c[x].size() - 1; i; --i) bit.update(c[x][i], a[c[x][i - 1]]);for (int i = c[x].size() - 1; i; --i) swap(a[c[x][i]], a[c[x][i - 1]]);
}void calculate() {while (Q--) {int op, l, r;read(op), read(l);if (op & 1) {read(r);int ans = bit.query(l, r);for (int x : ex) ans += deal(x, l, r);print(ans), putchar('\n');}else {if (c[l].size() <= B) swp(l);else ++tg[l];}}
}void solve() {init();calculate();
}signed main() {solve();return 0;
}
总结
这个题的 \(\rm{subtask}\) 提示的非常完备, 挨着想肯定能想出来
根号分治无敌, 这个题属于超级巨大根号分治, 害怕
多种做法可以用根号分治均摊, 前提是需要有可以分治的点