2018/07/19测试T2 序列切割(RMQ+动态规划)

【题目】

题目传送门Cashback

题目描述:

给定一个长度为 n 的序列 a_{i} ,和一个数字 c。你需要将这个序列切成若干段,对于每一个长度为 k 的数字段,这段中最小的 k/C 个数字(向下取整)都会自动删除,问如何切割使得最后剩下的数字和最小,最小是多少?

如序列 [ 3,1,6,5,2 ] 当 C = 2,这时 3 + 6 + 5 = 14

输入格式:

第一行是 2 个整数 n,c
第二行是 n 个整数 a_{i}

输出格式:

一个整数,最小值

样例数据:

输入
3 5
1 2 3

输出
6

输入
12 10
1 1 10 10 10 10 10 10 9 10 10 10

输出
92

备注:

数据规模与约定:
40%数据,n,c ≤ 2000
60%数据,n,c ≤ 10000
100%数据,n,c ≤ 100000,1 ≤ a_{i} ≤ 10^{9}

【分析】

我们分析一下以下几种情况

  • 若 n < c ,则不管怎么分都不会删去任何一个数
  • 若 n == c ,则最好的情况就是删去一个最小值
  • 若 c < n < 2 * c ,则原序列中最多出现一个完整的 c ,这意味着最多删去一个数
  • 若 n > 2 * c ,则每次都以 c 为长度分割,这样能使最终答案删数最多

实际上这是一道动态规划的题

我们定义 dp [ i ] 表示到 i 能删除数的最大值

最终的答案 answer = sum - dp [ n ]   (sum是所有数的总数)

那么转移方程为:dp [ i ] = max { dp [ i - 1 ],dp [ i - c ] + min { a[ k ] } }    (i - c + 1 ≤ a[ k ] ≤ i)

对于区间的最小值,我用的ST算法,当然线段树那些数据结构也是可以的(主要是ST算法代码简单一点)

【代码】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100005,M=25;
int n,c;
int f[N][M];
int a[N],Log[N];
long long dp[N];
void GetLog()
{
	int i;
	Log[1]=0;
	for(i=2;i<=n;++i)
	  Log[i]=Log[i/2]+1;
}
void RMQ()
{
	int i,j;
	for(i=1;i<=n;++i)
	  f[i][0]=a[i];
	for(j=1;(1<<j)<=n;++j)
	  for(i=1;i+(1<<(j-1))<=n;++i)
	    f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int main()
{
//	freopen("cut.in","r",stdin);
//	freopen("cut.out","w",stdout);
	int l,r,i,k;
	long long sum=0;
	scanf("%d%d",&n,&c);
	for(i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	RMQ();
	GetLog();
	for(i=c;i<=n;++i)
	{
		k=Log[c];
		l=i-c+1,r=i;
		dp[i]=max(dp[i-1],dp[i-c]+min(f[l][k],f[r-(1<<k)+1][k]));
	}
	printf("%lld",sum-dp[n]);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_dreams/article/details/81125903
今日推荐