[BJOI2019] 刪數 [dp轉貪心結論+線段樹]

題面

傳送門ios

思路

dp部分

如下稱合法序列爲原題面中能夠刪空的序列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$

Code

這代碼寫的我好氣啊

終端裏面寫完代碼,: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);
    }
}
相關文章
相關標籤/搜索