• CF1404C Fixed Point Removal


    题目大意

    题目链接

    给定一个正整数序列(a_{1},dots ,a_{n})。你可以进行若干次操作,每次操作可以选择一个满足(a_i=i)的位置(i),将其删去。删去后,左右两边会自动拼合到一起(也就是右边所有数下标都会相应地变化)。

    你想要最大化删去的元素数量。

    但是这个问题太简单了,所以你需要回答(q)次询问。每次询问给定两个正整数(x,y),求如果将序列的前(x)个数和后(y)个数变成(n+1)(也就是强制它们不得被删去),新的序列里最多能删掉多少个数呢?

    注意,每次询问是独立的,也就是说询问后,序列会复原。

    数据范围:(1leq n,qleq 3 imes 10^5)(1leq a_ileq n)(x,ygeq 0,x+y< n)

    本题题解

    先不考虑多次询问。只针对一个给定的序列,如何求答案呢?

    我们发现,后面的数被删除时,不会对前面的数产生影响。因此,我们总能通过巧妙地安排删除顺序,使得所有“理论上能被删除的数”都被删掉。

    例如,初始时的一个位置(i),满足(a_i=i-2)(也就是(a_i)需要向前移动(2)格才能被删掉)。假设,我们已经知道了(i)前面有(5)个能被删除的数(即我们能构造出一种删除它们的方案)。那我们只要在这个方案进行到第(2)步时(此时(a_i)向前移动了(2)格,满足了(a_i=i)),把(a_i)删掉。然后再继续删除后(3)个数即可。因为(i)的位置在它们后面,所以中间插入一个“删除(a_i)”的操作,不会对原本的构造产生影响。于是我们就在“删除前(i-1)个数的方案”的基础上,构造出了“删除前(i)个数的方案”。用这种归纳,可以证明上一段所说的“应删尽删”的结论。

    形式化地,我们设(f_i)表示序列的前(i)个位置,最多有多少位置被删掉。则:

    [f_i=f_{i-1}+[igeq a_i ext{ and }f_{i-1}geq i-a_i] ]

    如果没有多次询问,我们现在可以(O(n))求出一个序列的答案。


    考虑多次询问。

    此时有两种方向,一种是直接用数据结构维护区间,把整体问题搬到区间上,例如用线段树或者分块。但是我们发现,这个(f)是需要递推的,也就是说,前面一个(f)变化,可能会造成连锁反应。因此不好直接维护区间。

    另一种方向是发现问题性质中的可二分性。我们来具体说说。

    我们称【把序列的(x)个数强行变成(n+1)】为【ban掉(x)个数】。

    对于任何一个位置,显然是它前面ban掉的数越少,它越可能可以被删除。那么,对每个位置(i),预处理出一个( ext{lim}_i) ((-1leq ext{lim}_i<i)),表示ban掉(leq ext{lim}_i)个数时,(i)这个位置是可以被删除的;ban掉(> ext{lim}_i)个数,(i)这个位置就不能被删除了。这也就是我说的,“发现问题性质中的可二分性”。

    如何预处理( ext{lim}_i)?考虑对每个(i)二分答案。然后相当于要查询前(i-1)个位置里,满足( ext{lim}_jgeq ext{mid})(j)有多少个 ((1leq j<i))。可以用主席树维护。我们甚至可以直接在主席树上二分,这样复杂度从(O(nlog^2n))降为(O(nlog n))

    知道了( ext{lim}_1,dots , ext{lim}_n)后,如何求答案?相当于要查询,(jin[1,n-y])里,满足( ext{lim}_jgeq x)(j)有多少个。可以直接在主席树上查询。时间复杂度(O(qlog n))

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

    总结反思

    这个题给了我们一个很好的启示:处理多次询问的问题时,不一定要用数据结构大力维护出区间答案。可以尝试发现问题里的可二分性,然后实现“降维”。例如本题里,这种思路就成功地去掉了(x)这一维,我们只需要简单地用数据结构维护(y)的答案即可。

    有一类经典问题,多次询问,每次问一个区间([l_i,r_i])是否可行。如果发现了可二分性,那么一个做法是:对每个(rin[1,n]),预处理出能够使它合法的最小/最大的(l)。这个(l)可以二分,有时也可以直接用 two pointers 求。这个经典做法,与本题的思路多少有些异曲同工之妙。

    参考代码

    Codeforces提交记录

    //problem:CF1404C
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=3e5;
    int n,q,a[MAXN+5],b[MAXN+5],lim[MAXN+5];
    struct FrogTree{
    	int rt[MAXN+5],sum[MAXN*40],ls[MAXN*40],rs[MAXN*40],cnt;
    	void ins(int& x,int y,int l,int r,int pos){
    		x=++cnt;
    		sum[x]=sum[y]+1;
    		ls[x]=ls[y];
    		rs[x]=rs[y];
    		if(l==r) return;
    		int mid=(l+r)>>1;
    		if(pos<=mid)
    			ins(ls[x],ls[y],l,mid,pos);
    		else
    			ins(rs[x],rs[y],mid+1,r,pos);
    	}
    	int binary_search(int x,int l,int r,int b){
    		// 最短的, 和>=b的后缀
    		if(l==r)
    			return l;
    		int mid=(l+r)>>1;
    		if(sum[rs[x]] >= b)
    			return binary_search(rs[x],mid+1,r,b);
    		else
    			return binary_search(ls[x],l,mid,b-sum[rs[x]]);
    	}
    	int query(int x,int l,int r,int ql,int qr){
    		if(!x) return 0;
    		if(ql<=l && qr>=r){
    			return sum[x];
    		}
    		int mid=(l+r)>>1;
    		int res=0;
    		if(ql<=mid)
    			res += query(ls[x],l,mid,ql,qr);
    		if(qr>mid)
    			res += query(rs[x],mid+1,r,ql,qr);
    		return res;
    	}
    	FrogTree(){}
    }T;
    int main() {
    	cin>>n>>q;
    	for(int i=1;i<=n;++i){
    		cin>>a[i];
    		if(i<a[i]){
    			T.rt[i]=T.rt[i-1];
    			b[i]=-1;
    			lim[i]=-1;
    			continue;
    		}
    		b[i]=i-a[i];
    		if(T.sum[T.rt[i-1]] < b[i]){
    			lim[i]=-1;
    			T.rt[i]=T.rt[i-1];
    			continue;
    		}
    		if(!b[i])
    			lim[i]=i-1;
    		else
    			lim[i] = T.binary_search(T.rt[i-1],0,n-1,b[i]);
    		T.ins(T.rt[i],T.rt[i-1],0,n-1,lim[i]);	
    	}
    	//for(int i=1;i<=n;++i)
    	//	cerr<<lim[i]<<" ";
    	//cerr<<endl;
    	while(q--){
    		int x,y;
    		cin>>x>>y;
    		cout<<T.query(T.rt[n-y],0,n-1,x,n-1)<<endl;
    	}
    	return 0;
    }
    
  • 相关阅读:
    ASIHTTPRequest类库简介和使用说明
    UIDatePickerView实现时间滚动轮播效果
    UIPickerView选择控件实现选择轮播效果(转轮效果)
    懒加载三大优势
    UIView的自适应高度 (图像,文字)
    正则表达式校验yyyymmdd
    Java时间日期格式转换 转自:http://www.cnblogs.com/edwardlauxh/archive/2010/03/21/1918615.html
    java.sql.SQLException: ORA-00911: 无效字符 解决方法 引自: http://blog.csdn.net/yangzhijun_cau/article/details/6064956
    跨域访问
    maven + eclipse + tomcat热部署 引自:http://jingpin.jikexueyuan.com/article/23068.html
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13625607.html
Copyright © 2020-2023  润新知