P7603 [THUPC2021] 鬼街 题解
第一次见折半报警器的 trick,记录一下
首先观察到 \(x \le n \le 10^5\),所以 \(x\) 最多有 6 个质因数,\(x = 30030\) 可以取到,这使得对于修改,我们可以暴力单点修改。
接下来考虑询问,朴素的做法是:每一次灵异事件之后,都对所有监控器进行检验是否满足和为 \(y\)。
这样复杂度不对,但是我们可以注意到并不是所有时刻都需要对某些监控器进行检验。
这里有一个显然但是关键的结论:
如果 \(\sum_{1\le i\le k} a_i = y\),则 \(\exists a_i \ge \lceil\dfrac yk\rceil\),这里面的 \(a_i\) 表示从监控设置开始到监控报警时刻,\(i\) 号房的闹鬼次数。
反证法易证。
这启示我们,只在满足上必要条件的情况下,对于一个监控进行检验,检验之后更新限制为:\(\sum_{1\le i\le k}\Delta a_i = y - \sum_{1\le i\le k}a_{1i}\),其中 \(\Delta a_i\) 表示从这次检验时间开始,\(i\) 号房的闹鬼次数,\(a_{1i}\) 表示从上次检验/设置开始,到当前检验时间 \(i\) 号房的闹鬼次数。
由于 \(\exists a_{1i} \ge \lceil\dfrac yk\rceil\),所以 \(\sum_{1\le i\le k}a_{1i}\ge \lceil\dfrac yk\rceil\) 每进行一次检验,都会使得 \(y \leftarrow y - \lceil\dfrac yk\rceil\),相当于每次 \(y\) 会变为原来的 \(\dfrac{k-1}{k}\) 倍左右,所以最多进行 \(O(\log_{\frac{k}{k-1}}y)\) 次检验。
那么我们只需要在每个位置上设置一个 "报警器",在单点设置监控/检验之后,为这个点添加 "报警器" \((\lceil\dfrac yk\rceil, i)\),表示如果这个位置的闹鬼次数超过了 \(\lceil\dfrac yk\rceil\) 就需要检验 \(i\) 号监控。
然后每次修改一个点的时候就对这个点上最可能报警的 "报警器" 进行检查,即限制最小的报警器。
这些报警器使用 std::set
或者 __gnu_pbds::tree
可以维护,但是常数略大,因为每次只需要取出最小值,可以使用一个堆来维护。
本题中询问 \(m\) 与鬼屋个数 \(n\) 同阶,时间复杂度:\(O(n\log_{\frac k{k-1}} V\log n\omega(n))\)。
参考代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <ctime>
#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/assoc_container.hpp>
//#define int long long
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll, int> PII;
const int N = 1e5 + 10;int n, m, primes[N], tx[N], tot;
ll val[N], sback[N], a[N];
bool st[N];
void sieve() {for(int i = 2; i < N; i ++) {if(!st[i]) primes[++ tot] = i;for(int j = 1; j <= tot && primes[j] * i < N; j ++) {st[i * primes[j]] = 1;if(i % primes[j] == 0) break;}}
}
__gnu_pbds::priority_queue<PII, greater<PII>, __gnu_pbds::pairing_heap_tag> warn[N];
vector<__gnu_pbds::priority_queue<PII, greater<PII>, __gnu_pbds::pairing_heap_tag>::point_iterator> back[N];
vector<int> que, pr[N];
void check(int i) {ll s = 0, x = tx[i], y = val[i];for(auto d : pr[x]) s += a[d];y -= s - sback[i], val[i] = y;for(int id = 0, d; id < pr[x].size(); id ++) d = pr[x][id], warn[d].erase(back[i][id]);if(y <= 0) {que.push_back(i);return ;}ll lm = (y + (int)pr[x].size() - 1) / (int)pr[x].size();sback[i] = s;for(int id = 0, d; id < pr[x].size(); id ++) {d = pr[x][id];back[i][id] = warn[d].push({lm + a[d], i});}
}signed main() {ios::sync_with_stdio(0), cin.tie(0);cin >> n >> m, sieve();clock_t stt =clock();for(int i = 1; i <= tot; i ++)for(int p = primes[i], j = p; j <= n; j += p)pr[j].push_back(p);int cnt = 0;for(ll i = 1, op, x, y, lst = 0; i <= m; i ++) {cin >> op >> x >> y, y ^= lst;if(op == 1) {cnt ++;tx[cnt] = x, val[cnt] = y;ll lm = (y + (int)pr[x].size() - 1) / (int)pr[x].size();if(y == 0) que.push_back(cnt);else {back[cnt].resize(pr[x].size(), 0);sback[cnt] = 0;for(int id = 0, d; id < pr[x].size(); id ++) {d = pr[x][id];sback[cnt] += a[d];back[cnt][id] = warn[d].push({lm + a[d], cnt});}}}else {for(auto d : pr[x]) a[d] += y;for(auto d : pr[x]) {while(warn[d].size() && (warn[d].top().x <= a[d])) check(warn[d].top().y);}sort(que.begin(), que.end());cout << que.size() << ' ';for(auto x : que) cout << x << ' '; cout << '\n';lst = que.size();que.clear();}}return 0;
}