当前位置: 首页 > news >正文

CF1404C Fixed Point Removal

CF1404C Fixed Point Removal

又一道独立做出来的 CF2300,喜。

第一步转化

第一步转化是显然的,我们记 \(b_i = i - a_i\),于是对于一个给定的询问 \([l, r]\),问题转化为:

从左往右扫,有多少个位置 \(i\) 满足 \(i\) 前面能删除的个数小于 \(b_i\)

直接做是 \(O(qn)\) 的。

分组

考虑试着从右往左加数,看看我们能不能发现什么有用的性质。于是观察到每个位置其实都对应着一个能删除自己的最晚的起点,不难发现这个位置的值一定是 \(0\)。记 \(c_i\) 表示 \(i\) 的最晚起点。

现在考虑怎么求 \(c_i\)

考虑把相邻的 \(0\) 之间的部分划分成一组(或者说一段),于是一组内的点被分成了三类:

1、可以从本组的 \(0\) 出发被删除。

2、可以从前若干组的 \(0\) 出发被删除。

3、不能被删除。

对于 \(1\) 类点和 \(3\) 类点,我们可以分组遍历进而求得 \(c_i\)

对于 \(2\) 类点,假设 \(i\) 位于以第 \(j\)\(0\) 为起点的组,记 \(sum_j\) 表示第 \(j\) 组中 \(1\) 类点的个数,\(s_i\) 表示在 \(i\) 前面有多少个本组的 \(1\) 类点。我们可以二分一个位置 \(x\),使其恰好满足 \(sum_{x \sim j - 1} + s_i \ge b_i\)。那么 \(c_i = x\)

现在考虑怎么维护 \(sum_i\)

\(sum_i\) 看似是静态的,但其实每找到一个位置的 \(c_i\)\(sum_{c_i}\) 就要 \(+1\)(很好理解)。所以顺便说一句,求 \(c_i\) 的过程的扫描顺序也必须是从左往右。

所以用树状数组支持单点修,区间查就可以了。

于是我们在 \(O(n \log^2n)\) 的时间内得到了 \(c_i\)

查询

很经典的离线套路:因为当右端点固定时,答案一定是随着左端点减小而增大,所以我们把询问挂在左端点上,并从右往左处理这些询问。

具体的处理方法是:从右往左一组一组的处理。假设现在在第 \(i\) 组,对于一个给定的询问 \([l, r]\),答案可以转化为:\(\sum_{j = l}^{r}{[c_j \ge i]}\)

于是每加入一个新的组 \(i\),就先把 \(c_j = i\) 的所有 \(j\) 加入值域树状数组中,然后就可以 \(O(\log n)\) 地回答每个询问了。

总时间复杂度 \(O(n \log^2n + q \log n)\)

注意一共开了两个树状数组。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l); i <= (r); ++ i)
#define G(i,r,l) for(int i(r); i >= (l); -- i)
#define pii pair<int, int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
using ll = long long;
const int N = 4e5;
const int inf = 1e9; 
int n, q, cnt = 0;
int R[N], L[N], a[N], b[N], c[N], ans[N], s[N], pos[N];
vector<int> g[N];
vector<pii> Q[N];
struct Tree{int sum[N], num;int lowbit(int x){return (x & -x);}void add(int x, int y){for(; x <= num; x += lowbit(x)) sum[x] += y; }int ask(int x){int ret = 0;for(; x >= 1; x -= lowbit(x)) ret += sum[x];return ret;}
}tr, sg;
void solve(int l, int r){ // 本组的0到下一组的0的前一位 ++ cnt;R[cnt] = r;L[cnt] = l;int nw = 0;F(i, l, r){if(b[i] >= 0 && b[i] <= nw){c[i] = cnt;++ nw;}s[i] = nw;pos[i] = cnt;}
}
signed main(){ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);cin >> n >> q;F(i, 1, n){cin >> a[i];b[i] = i - a[i];c[i] = inf;}int o = 0;F(i, 1, n) if(b[i] == 0){o = i;break;}if(o == 0){F(i, 1, q) cout << "0\n";return fflush(0), 0;}F(i, o + 1, n){if(b[i] == 0){solve(o, i - 1);o = i;}}solve(o, n);tr.num = cnt;F(i, 1, cnt) tr.add(i, s[R[i]]);F(i, 1, n){if(b[i] >= 0 && c[i] == inf){int l = 0, r = pos[i] + 1, mid, tmp = b[i] - s[i], tot = tr.ask(r - 2); while(l + 1 < r){mid = (l + r) / 2;int ret = tot - tr.ask(mid - 1); if(ret >= tmp) l = mid;else r = mid;}if(l != 0){c[i] = l; tr.add(l, 1);}}}F(i, 1, n) if(c[i] != inf) g[c[i]].push_back(i);F(i, 1, q){int x, y;cin >> x >> y;Q[x + 1].push_back(mp(i, n - y));}sg.num = n;G(i, cnt, 1){for(auto j : g[i]) sg.add(j, 1);int l, r;if(i > 1){l = L[i - 1] + 1;r = R[i - 1] + 1;}else{l = 1;r = L[i];}F(j, l, r){for(auto v : Q[j]){ans[v.fi] = sg.ask(v.se);}}} F(i, 1, q) cout << ans[i] << '\n';return fflush(0), 0;
}
http://www.sczhlp.com/news/8528/

相关文章:

  • 竞赛日记
  • 基本的DOS命令
  • vue2.6和vue2.7的差异是什么?
  • vscode使用region折叠任意想折叠的代码块
  • 第十五天
  • 第十六天
  • 第十七天
  • 第十八天
  • Spring架构原理 Spring内存马
  • SnakeYaml反序列化
  • 20250808 之所思 - 人生如梦
  • Tomcat 架构原理 Tomcat内存马
  • 【RC电子硬件】180舵机的安装与调试
  • 遗忘的物语:蜥蜴与神明的寓言
  • 560. 和为 K 的子数组
  • java学习(8月8日)
  • iPhone12换电池拆机拆屏幕成功案例与材料
  • 模版
  • 【举例】小数点在计算机中的存储标准举例 20.625
  • Solidity-101
  • 202507做题记录
  • 202508做题记录
  • C#学习
  • MySQL之Group By优化
  • 2025年8月8日
  • 8.8 说点心里话
  • Java Agent使用
  • 常识判断
  • 【LeetCode 108】算法:将有序数组转换为二叉搜索树
  • 25.8.7python模块1