CF#Div2668 E - Fixed Point Removal
一道挺巧妙的数据结构题
链接https://codeforc.es/contest/1405/problem/E
首先一个简单的预处理把题目中的x,y转化成我们熟悉的[l,r]区间
不妨设f(l,r)为从l开始,到r最多可以remove掉几个元素,发现可不可以移掉一个元素和它的位置还有值有关
我们不妨令a[i] = i - a[i],然后问题就转化为a[i] = 0可以remove,然后可以发现对于询问左端点l,一个位置pos有贡献的充要条件是a[pos] >= 0 && a[pos] <= f(l,r-1)
即f(l,r) = f(l,r-1) + [a[r] >= 0 && a[pos] <= f(l,r-1)]
解释一下这个状态转移方程,首先a[r]肯定要>=0,因为如果一开始就已经小于0了,那么无论前面删掉多少数,a[i]也不可能 = 0,至于小于等于是因为你可以在前面删了a[r]个数时(按照定义此时a[r] == 0)先把r这个位置删了
然后再删剩下的数(类似反悔贪心?).
(然后我就sb地不会做了)
其实我们可以发现每新增一个数,所有的l转移都一样,且只有满足f(l,x) >= a[pos]的才能转移,于是我们可以用树状数组动态地维护这个序列 f(1,r),f(2,r).......f(r,r),显然这个序列就有单调性,于是我们可以每次二分lmax满足f(lmax,r-1) >= a[r],然后对于1~lmax区间加1,把所有询问以右端点为下标存在一个vector里,每次处理到一个右端点就把所有询问都拿出来在树状数组上查询一遍
代码如下
/*E. Fixed Point Removal*/ #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<vector> using namespace std; #define ll long long int read(){ char c = getchar(); int x = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar(); return x; } const int maxn = 3e5 + 10; int a[maxn]; vector<int>L[maxn]; vector<int>id[maxn]; int ans[maxn]; #define lowbit(x) (x & (-x)) int bit[maxn]; int n,q; int ask(int x){ int ans = 0; for(; x; x -= lowbit(x)) ans += bit[x]; return ans; } void add(int x,int y){ if(x <= 0) return; for(; x <= n; x += lowbit(x)) bit[x] += y; } int main(){ n = read();q = read(); for(int i = 1; i <= n; ++i){ a[i] = i - read(); } for(int i = 1; i <= q; ++i){ int l = read(),r = read(); l = 1 + l; r = n - r; L[r].push_back(l); id[r].push_back(i); } for(int i = 1; i <= n; ++i){ if(a[i] >= 0){ int l = 1,r = i; int pos = 0; while(l <= r){ int mid = (l + r) >> 1; if(ask(mid) >= a[i]){ l = mid + 1; pos = mid; } else r = mid - 1; } if(pos != 0){ add(1,1); add(pos+1,-1); } } for(int j = 0; j < (int)L[i].size(); ++j){ int l = L[i][j]; int Id = id[i][j]; ans[Id] = ask(l); } } for(int i = 1; i <= q; ++i){ printf("%d\n",ans[i]); } return 0; }