0719-动态规划+ST表优化-CF 940E

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;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/81122660
今日推荐