Loading

01.03 CW 模拟赛 T4. ring

前言

找原题未遂了()
\(\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_unlocked

template <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}\) 提示的非常完备, 挨着想肯定能想出来

根号分治无敌, 这个题属于超级巨大根号分治, 害怕

多种做法可以用根号分治均摊, 前提是需要有可以分治的点

posted @ 2025-01-04 09:20  Yorg  阅读(19)  评论(0)    收藏  举报