【题目】
题目传送门Cashback
题目描述:
给定一个长度为 n 的序列 ,和一个数字 c。你需要将这个序列切成若干段,对于每一个长度为 k 的数字段,这段中最小的 k/C 个数字(向下取整)都会自动删除,问如何切割使得最后剩下的数字和最小,最小是多少?
如序列 [ 3,1,6,5,2 ] 当 C = 2,这时 3 + 6 + 5 = 14
输入格式:
第一行是 2 个整数 n,c
第二行是 n 个整数
输出格式:
一个整数,最小值
样例数据:
输入
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 ≤ ≤
【分析】
我们分析一下以下几种情况
- 若 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;
}