• CodeForces 1326E


    思维题杀我!现场(^*2600)的状压DP都会做,(^*2400)的思维题就不会了,看来是wtcl/ll

    洛谷题目页面传送门 & CodeForces题目页面传送门

    给定(2)(1sim n)的排列(a,b)(a)上某些位置会存在炸弹。对于某些位置有炸弹的(a),维护一个集合,初始为空,从左往右扫描整个排列(a),每到一个位置上就将此位置上的数压进集合,若此位置有炸弹就把集合中最大的数弹出,我们称最终集合中最大的元素为(a)的结果。(forall iin[0,n)),求若在(forall jin[1,i],b_i)这些位置放下炸弹,结果是多少。

    (ninleft[1,3 imes10^5 ight])

    显然,依次算每个结果的话,第(i)次被弹出的集合是第(i+1)次被弹出的集合的子集,即今朝被弹出,永远就不会复活了。可以推出这(n)个结果非严格单调递减。于是我们可以two-pointers,维护目前最大的没有被弹出的数(now),初始时(now=n),每次算答案时不停地令(now=now-1)直到(now)没有被弹出。难点在于如何快速判断(now)是否被弹出。

    如果真就一直在想(now)被弹出的充要条件,恭喜你,你被魔法杀死了。不难发现一个性质:无论何时,只要你正在考虑判断(now)是否被弹出,那么一定所有(>now)的数已经确认被弹出了(因为如果不是那样,(now)自减的过程就会在某个(>now)的数处停下)。于是我们所考虑的(now)被弹出,其实等价于所有(geq now)的数都被弹出。这个东西的充要条件看上去容易探索一点(然鹅的确是这样)。

    下面我们来探索所有(geq now)的数都被弹出的充要条件。考虑每个(geq now)的数(a_x),显然对于每个在(a_x)右边且(geq now)的数(a_y)(包括(a_x)自己),弹出它们的炸弹在(a_y)右边(包括(a_y)位置上),结合(a_y)(a_x)右边可以得到弹出它们的炸弹一定在(a_x)右边(包括(a_x)位置上)。于是我们得到了所有在(a_x)右边且(geq now)的数(包括(a_x)自己)都被弹出的一个很弱的必要条件:(a_x)右边的炸弹(包括(a_x)位置上)数量不低于在(a_x)右边且(geq now)的数(包括(a_x)自己)的数量。充分性显然不满足。

    不过,如果将(forall x(a_xgeq now)),所有在(a_x)右边且(geq now)的数(包括(a_x)自己)都被弹出的这么多必要条件合并起来,得到所有(geq now)的数都被弹出的一个必要条件:(forall x(a_xgeq now))(a_x)右边的炸弹(包括(a_x)位置上的)数量不低于在(a_x)右边且(geq now)的数(包括(a_x)自己)的数量。你会发现这个条件似乎很强,于是我们试图证明它的充分性。然后真就证出来了!

    充分性证明(感性):找到(a_x=n),显然,(a_x)右边(包括(a_x)位置上)的最左边的炸弹与(a_x)匹配,于是我们可以想象将这个炸弹扔掉,并将(a_x)扔出排列,两边挨紧形成一个新的(1sim n-1)的排列。(a_x)左边所有(geq now)的数右边(geq now)的数(包括自己)数量(-1),右边的炸弹(包括自己位置上)数量(-1)(a_x)右边、炸掉(a_x)的炸弹左边(包括炸弹位置上)所有(geq now)的数,右边(geq now)的数(包括自己)的数量不变,由于(a_x)与炸弹之间没有炸弹,所以它们右边的炸弹(包括自己位置上)数量至少剩(a_x)原来右边炸弹(包括自己位置上)的数量(-1);炸弹右边所有(geq now)的数右边(geq now)的数(包括自己)的数量、炸弹(包括自己位置上)数量都没变。由此看来条件依然满足。于是原证明题可以转化为一个规模(-1)的证明题,可以用数学归纳法加以证明。

    有了这个结论,接下来就好办了。设(a_i)右边(geq now)的数(包括(a_i)自己)有(grt_i)个,右边的炸弹(包括(a_i)位置上)有(bmb_i)个。那么(forall x(a_xgeq now))(a_x)右边的炸弹(包括(a_x)位置上的)数量不低于在(a_x)右边且(geq now)的数(包括(a_x)自己)的数量,可以写成(forall x(a_xgeq now),bmb_xgeq grt_x),即(forall x(a_xgeq now),bmb_x-grt_xgeq0)。于是我们需要维护(forall iin[1,n],bmb_i-grt_i)。判断(now)是否被弹出时只需判断全局最小值是否(geq0),每当(now=now-1)时(初始(now=n)时也要)就将位置(left(a^{-1} ight)_{now})上的(bmb_{left(a^{-1} ight)_{now}}-grt_{left(a^{-1} ight)_{now}})激活(即今后算进全局最小值,激活之前懒标记可以帮忙保存(bmb_{left(a^{-1} ight)_{now}}-grt_{left(a^{-1} ight)_{now}}))并令(forall iinleft[1,left(a^{-1} ight)_{now} ight],grt_i=grt_i+1),每当新增一个炸弹(b_x)就令(forall iin[1,b_x],bmb_i=bmb_i+1)。这只需要单点修改、区间增加、全局查询最小值的线段树即可实现。

    下面是AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int inf=0x3f3f3f3f;
    const int N=300000;
    int n;//排列长度 
    int a[N+1]/*排列*/,b[N+1]/*炸弹添加顺序*/;
    int pos[N+1];//pos[i]为数i在a中的位置 
    struct segtree{//线段树 
    	struct node{int l,r,mn_dif/*此节点表示的区间内bmb[i]-grt[i]的最小值*/,lz/*蓝标记*/;}nd[N<<2];
    	#define l(p) nd[p].l
    	#define r(p) nd[p].r
    	#define mn_dif(p) nd[p].mn_dif
    	#define lz(p) nd[p].lz
    	void bld(int l=1,int r=n,int p=1){//建树 
    		l(p)=l;r(p)=r;mn_dif(p)=inf;/*一开始都是未激活状态*/lz(p)=0;
    		if(l==r)return;
    		int mid=l+r>>1;
    		bld(l,mid,p<<1);bld(mid+1,r,p<<1|1);
    	}
    	void init(){bld();}//初始化 
    	void sprup(int p){mn_dif(p)=min(mn_dif(p<<1),mn_dif(p<<1|1));}//上传节点信息 
    	void sprdwn(int p){//下传懒标记 
    		if(lz(p)){
    			mn_dif(p<<1)+=lz(p);mn_dif(p<<1|1)+=lz(p);
    			lz(p<<1)+=lz(p);lz(p<<1|1)+=lz(p);
    			lz(p)=0;
    		}
    	}
    	void on(int x,int p=1){//激活位置x 
    		if(l(p)==r(p)){mn_dif(p)=lz(p)/*之前的bmb[l(p)]-grt[l(p)]保存在lz(p)里*/;return;}
    		sprdwn(p);
    		int mid=l(p)+r(p)>>1;
    		on(x,p<<1|(x>mid));
    		sprup(p);
    	}
    	void add(int l,int r,int v,int p=1){//区间增加 
    		if(l<=l(p)&&r>=r(p)){mn_dif(p)+=v;lz(p)+=v;return;}
    		sprdwn(p);
    		int mid=l(p)+r(p)>>1;
    		if(l<=mid)add(l,r,v,p<<1);
    		if(r>mid)add(l,r,v,p<<1|1);
    		sprup(p);
    	}
    	int _mn_dif(){return mn_dif(1);}//全局最小值 
    }segt;
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++)cin>>a[i],pos[a[i]]=i;
    	for(int i=1;i<=n;i++)cin>>b[i];
    	int now=n;//初始now=n 
    	cout<<now<<" ";//不放炸弹时 
    	segt.init();
    	segt.on(pos[n]);
    	segt.add(1,pos[n],-1);//增加一个>=now的数 
    	for(int i=1;i<n;i++){
    		segt.add(1,b[i],1);//增加一个炸弹 
    		while(segt._mn_dif()>=0/*now被弹出*/){
    			now--;
    			segt.on(pos[now]);
    			segt.add(1,pos[now],-1);//增加一个>=now的数 
    		}
    		cout<<now<<" ";
    	}
    	return 0;
    }
    
  • 相关阅读:
    模板集合
    [NOIP2005普及组]循环(高精度+数学)
    KEYENCE Programming Contest 2021
    AtCoder Regular Contest 111
    Educational Codeforces Round 99 (Rated for Div. 2)
    AtCoder Beginner Contest 183翻车记
    上古退役选手康复训练1——CSP2020J-2
    [SNOI2020]取石子(数学+打表找规律)
    selenium爬取拉勾网招聘信息
    scrapy将爬取的数据存入MySQL数据库
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-1326E.html
Copyright © 2020-2023  润新知