这一段一直在CF上刷DP专项,虽然在考试的时候正解经常还想不出来,但是现在对于一道题仔细想一想n^2,n^3的暴力最起码会写了......
分析:刚看到这道题时其实感觉挺玄乎的,自己在纸上写写画画一直也没有什么头绪,后来自己看数据范围强行手弄了一个n^3的暴力,就是定义dp[i]表示序列前i的最优答案,我们就可以枚举断点来进行区间拼接于是有dp[i]=min(dp[i],dp[j]+sum),由于需要O(n)计算前k小,成功的倒在了第七个点的2000的数据下......
对于这道题我们可以想象一下,如果我们有一个长度不足以去除该区间内数字的小数列,那么其实它和将它分为一个一个单独的数字之后的效果是等效的,后者貌似还更好处理,那我们当然要用好处理的来计算,另外,如果我们选择了一个大区间,里面我们可以去除很多的数字,其实我们可以理解为就是每c的长度可以去除里面的一个最小的数字,之后再将相邻的长度为c的序列进行拼接,我们相当于就是向不同长度为c的序列之间填了一些数字,由于数字的大小这些数字对最终答案并没有什么影响,将他们和长度为c的序列分开,让它们单独成为一个序列似乎也可以,而且更好处理,和前面的处理方案相同。这样我们就可以把最终的答案序列理解为若干长度为c的序列和一些单独的点的集合,由于每个区间我们只除去一个数字即可,即最小值,我们就避免了求区间前k小的数字的麻烦,直接用一个线段树维护就可以了(在上午的时候思考如何求前k小卡了我好一阵QAQ),之后我们就可以尝试将区间划分一下,处理出每个区间的最小值,尝试让这些数字的总和最大,我们就可以得到最小的答案了。
1 #include<bits/stdc++.h> 2 #define int long long 3 #define lson (t<<1) 4 #define rson (t<<1|1) 5 #define mid ((l+r)>>1) 6 using namespace std; 7 const int N=1e5+10; 8 int dp[N],a[N]; 9 int b[N]; 10 int tree[N]; 11 void build(int t,int l,int r){ 12 if(l==r){ 13 tree[t]=a[l]; 14 return; 15 } 16 build(lson,l,mid); build(rson,mid+1,r); 17 tree[t]=min(tree[lson],tree[rson]); 18 } 19 int query(int t,int l,int r,int ql,int qr){ 20 if(ql<=l && r<=qr){ 21 return tree[t]; 22 } 23 if(qr<=mid) return query(lson,l,mid,ql,qr); 24 else if(ql > mid) return query(rson,mid+1,r,ql,qr); 25 else return min(query(lson,l,mid,ql,qr),query(rson,mid+1,r,ql,qr)); 26 } 27 int cnt[N],tot; 28 signed main(){ 29 int n,c; 30 scanf("%lld%lld",&n,&c); 31 dp[0]=0; 32 for(int i=1;i<=n;++i) scanf("%lld",&a[i]),tot+=a[i]; 33 build(1,1,n); 34 int ans=0; 35 for(int i=1;i<=n-c+1;++i) 36 cnt[i+c-1]=query(1,1,n,i,i+c-1); 37 for(int i=c;i<=n;++i) 38 dp[i]=dp[i-c]+cnt[i]; 39 for(int i=1;i<=n;++i){ 40 dp[i]=max(dp[i],dp[i-1]); 41 if(i>=c) dp[i]=max(dp[i],dp[i-c]+cnt[i]); 42 } 43 printf("%lld ",tot-dp[n]); 44 return 0; 45 }