CF940E 序列切割
描述
给定一个长度为n的序列ai,和一个数字c。你需要将这个序列切成若干段,对于每一个长度为k的数字段,这段中最小的k/C个数字(向下取整)都会自动删除,问如何切割使得最后剩下的数字和最小,最小是多少?
如序列[3,1,6,5,2]当C=2,这是3+6+5=14
输入
第一行是2个整数N,C
第二行是N个整数Ai
输出
一个整数,最小值
样例输入
【样例输入1】
3 5
1 2 3
【样例输出1】
6
【样例输入2】
12 10
1 1 10 10 10 10 10 10 9 10 10 10
【样例输出2】
92
【样例2解释】
其中一个最佳分区分别是[1,1],[10,10,10,10,10,10,10,10,10,10],其值分别为2和90。
提示
40%数据n,c<=2000
60%数据n,c<=10000
100%数据n,c<=100000,1<=ai<=10^9
分析:
若 n < c ,则不管怎么分都不会删去任何一个数
若 n == c ,则最好的情况就是删去一个最小值
若 c < n < 2 * c ,则原序列中最多出现一个完整的 c ,这意味着最多删去一个数
若 n >2 * c ,则每次都放完整的c为最佳答案,因为连续放在一起的c就会形成2 * c ,3 * c 等。
(虽然老师说这是很明显的动态规划,但是蒟蒻表示没有啊)
我们用 ans [ i ]表示现在选到 i 已经删除的最大值
那么对于每一个 i 而言,有两种情况
1.将它划分在长为c的序列中
2.不将它放在序列中(相当于是零散的)
对应这两种情况就可以得出状态转移方程了: ans[i]=max ( ans[i-c]+min { a[ k ] } , ans[i-1] ); ( k >= i - c + 1&&k <= i )
然后对于min { a [ k ] },我们可以用之前才讲的ST表进行优化
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 100009
using namespace std;
int n,c;
long long sum=0,a[N],maxn=-1,ans[N];
long long dp[N][25];
int Log[N];
void init(){
int i,j;
Log[1]=0;
for(i=2;i<=N;++i)
Log[i]=Log[i/2]+1;//int-->向下取整
}
void work(){
int i,j,k;
memset(dp,0x3f3f3f3f,sizeof(dp));
for(i=1;i<=n;++i) dp[i][0]=a[i];
for(j=1;j<=Log[n];++j)
for(i=1;i+(1<<j-1)<=n;++i)
dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
int main(){
scanf("%d%d",&n,&c);
int i,j,k;
long long minn=1e10;
for(i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
sum+=a[i];
minn=min(minn,a[i]);
}
if(c==1) printf("0");
else{
if(n<c) printf("%lld",sum);
else{
if(n==c) printf("%lld",sum-minn);
else {
init();
work();
for(i=c;i<=n;++i){
int l=i-c+1,r=i;
k=Log[r-l];
minn=min(dp[l][k],dp[r-(1<<k)+1][k]);
ans[i]=max(ans[i-c]+minn,ans[i-1]);
}
printf("%lld",sum-ans[n]);
}
}
}
return 0;
}