傳送門ios
如下稱合法序列爲原題面中能夠刪空的序列git
這個是我在模擬考場上的思路vim
一開始我是以爲,這個首先能夠寫成一個dp的形式:$dp[i][j]$表示用$j$個數字填滿了目標序列的前$i$須要的步數數據結構
而後,發現只有$dp[i][i]$有意義,因此優化爲$dp[i]$表示達成了構成長度爲$i$的序列須要的最小步數優化
猜一個轉移方程:$dp[i]=min_{j\in[1,i-1]}(dp[j]+max(0,(i-j)-num[i])$ui
這裏$num[i]$表示當前詢問的序列中數字$i$的出現次數,就是一個桶spa
轉移方程的意義就是在一個長度爲$j$的合法序列(下稱$j$序列)後面接上$i-j$個$i$正確性證實以下:code
首先,咱們能夠把原序列中全部等於$i$的數字直接用上,夠了就不用變新的,不夠了就從別的數字那裏拿一些變過來補上get
問題:如何肯定數字等於$i$的沒有在前面的$j$序列中被轉移過去?編譯器
能夠發現這裏不須要考慮這種狀況:轉移過去的只須要是和在前面須要的數大小不同的
$i$顯然不和$j$序列中任何一個數相等,因此即便用過了$i$,也能夠隨便挑一個$i$之外的代替$i$進去前面
而後由於最終一共$n$個數填長度爲$n$的序列,數老是夠用的,因此這就完成了$dp$轉移正確性的證實
這樣能夠得到47分
上面那個結論其實還有用處:它是貪心作法的基礎
能夠發現只有在$[1,n]$區間內的數是「有用」,即有可能不須要改變就能夠放進合法序列裏的
咱們考慮這些數的「有用性」,發現:對於$j$個$i$,裏面最多有$i$個是有用的
同時,若是有兩種數$i<j$,令$k=j-i$,若值爲$j$的數的數量大於$k$,那麼這裏$j$和$i$就會「衝突」,也就是$i$和如下的一小段位置只有一種數能夠不修改放進去
形式化地來講,設值爲$i$的數字有$cnt[i]$個,那麼它們能夠覆蓋區間$[i-cnt[i]+1,i]$
顯然,區間$[1,n]$內沒有被覆蓋的位置總數,就是這個詢問的答案
顯然咱們能夠經過線段樹維護最小值和最小值個數來解決這個問題
考慮兩種修改
第一種修改是修改兩個區間的長度,就是兩次單點修改而已
第二種修改能夠至關於平移詢問區間
這裏要注意:只有值在詢問區間裏的位置才能往前覆蓋
也就是說,假設我詢問的是$[2,5]$,原序列裏面有$3$個$6$,那這些6都沒有用
因此須要在第二種修改的時候看看會不會加入or刪除整個區間
實現上由於可能會減到負數,負數還能往下覆蓋,因此一共開長度爲$2n+2m$的線段樹,初始0位置$n+m$
這代碼寫的我好氣啊
終端裏面寫完代碼,:wq退出vim,而後習慣性按兩下上箭頭用gedit t1.cpp打開代碼往虛擬機外面複製
結果按兩下向上卻執行了從外部copy模板到t1.cpp,而後寫好的代碼就被覆蓋了......
而後我手忙腳亂,想從新打開編譯好的執行文件,結果又不當心按上執行了編譯命令,編譯一個沒有main()的模板
隨着編譯器提示編譯失敗,我原來的可執行程序也消失了......decompile都莫得機會
只好從新寫一遍......
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cassert> #define ll long long using namespace std; inline int read(){ int re=0,flag=1;char ch=getchar(); while(!isdigit(ch)){ if(ch=='-') flag=-1; ch=getchar(); } while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar(); return re*flag; } int n,m,a[1000010],cnt[1000010],ori[1000010],pos; struct ele{//合併用的線段樹元素,query的時候很方便,merge就行了 int minn,cnt; ele(int mm=0,int cc=0){minn=mm;cnt=cc;} friend inline ele merge(const ele &a,const ele &b){ ele re;re.minn=min(a.minn,b.minn); if(re.minn==a.minn) re.cnt+=a.cnt; if(re.minn==b.minn) re.cnt+=b.cnt; assert(re.minn>=0); return re; } }seg[2000010];int tag[2000010];//有區間修改,加lazytag inline void pushtag(int num,int w){seg[num].minn+=w;tag[num]+=w;assert(seg[num].minn>=0);} inline void push(int l,int r,int num){ if(l==r||!tag[num]) return; pushtag(num<<1,tag[num]); pushtag(num<<1|1,tag[num]); tag[num]=0; } inline void build(int l,int r,int num){ if(l==r){seg[num]=ele(ori[l],1);return;} int mid=(l+r)>>1; build(l,mid,num<<1);build(mid+1,r,num<<1|1); seg[num]=merge(seg[num<<1],seg[num<<1|1]); } inline void change(int l,int r,int num,int pos,int w){ if(l==r){seg[num].minn+=w;return;} int mid=(l+r)>>1;push(l,r,num); if(mid>=pos) change(l,mid,num<<1,pos,w); else change(mid+1,r,num<<1|1,pos,w); seg[num]=merge(seg[num<<1],seg[num<<1|1]); } inline void add(int l,int r,int ql,int qr,int num,int w){ if(l>=ql&&r<=qr){pushtag(num,w);return;} int mid=(l+r)>>1;push(l,r,num); if(mid>=ql) add(l,mid,ql,qr,num<<1,w); if(mid<qr) add(mid+1,r,ql,qr,num<<1|1,w); seg[num]=merge(seg[num<<1],seg[num<<1|1]); } inline ele query(int l,int r,int ql,int qr,int num){ if(l>=ql&&r<=qr) return seg[num]; int mid=(l+r)>>1;ele re(2e9,0);push(l,r,num); if(mid>=ql) re=merge(re,query(l,mid,ql,qr,num<<1)); if(mid<qr) re=merge(re,query(mid+1,r,ql,qr,num<<1|1)); return re; } int main(){ n=read();m=read();int i,t1,t2,j,tot=n+n+m+m;ele ans; pos=n+m; for(i=1;i<=n;i++) cnt[(a[i]=read()+pos)]++; for(i=1;i<=tot;i++) if(cnt[i]) for(j=i;j>i-cnt[i];j--) ori[j]++; build(1,tot,1); while(m--){ t1=read();t2=read(); if(t1){ //直接維護,注意不在目前範圍內的位置不要change!!! t2+=pos; cnt[a[t1]]--;ori[a[t1]-cnt[a[t1]]]--; if(a[t1]<=pos+n&&a[t1]>pos) change(1,tot,1,a[t1]-cnt[a[t1]],-1); a[t1]=t2; if(a[t1]<=pos+n&&a[t1]>pos) change(1,tot,1,a[t1]-cnt[a[t1]],1); ori[a[t1]-cnt[a[t1]]]++;cnt[a[t1]]++; } else{ //查看是否有區間須要加入or刪除!!! if(~t2){ pos--; if(cnt[pos+n+1]) add(1,tot,pos+n+1-cnt[pos+n+1]+1,pos+n+1,1,-1); if(cnt[pos+1]) add(1,tot,pos+1-cnt[pos+1]+1,pos+1,1,1); } else{ pos++; if(cnt[pos+n]) add(1,tot,pos+n-cnt[pos+n]+1,pos+n,1,1); if(cnt[pos]) add(1,tot,pos-cnt[pos]+1,pos,1,-1); } } ans=query(1,tot,pos+1,pos+n,1); if(ans.minn) puts("0"); else printf("%d\n",ans.cnt); } }