[BJOI2019] 删数 [dp转贪心结论+线段树]
题面
思路
dp部分
以下称合法序列为原题面中可以删空的序列
这个是我在模拟考场上的思路
一开始我是觉得,这个首先可以写成一个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])$
这里$num[i]$表示当前询问的序列中数字$i$的出现次数,就是一个桶
转移方程的意义就是在一个长度为$j$的合法序列(下称$j$序列)后面接上$i-j$个$i$正确性证明如下:
首先,我们可以把原序列中所有等于$i$的数字直接用上,够了就不用变新的,不够了就从别的数字那里拿一些变过来补上
问题:如何确定数字等于$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);
}
}