题意:给你n个数字,要求将连续的数字划分成x组,每组的和为Sumi,每组长度即数字个数为Li,但每组前 Li/c(向下取整)小的数不计入总和,求最小的和 ∑sumi
明显的dp,dp[i]表示前i个数取最优分组的结果
最容易想到的是n2的,也必然是TLE的解法(因为n是1e5)
dp[i] = min(dp[j] + sum[i]-sum[j] - sum(Minsum((i-j)/c)))
枚举1到j,将1到j中前(i-j)/c小的去除即可
但是这样不仅超时,也没有一个好方法记录不同(i-j)间前几小的
想了很久我也不知道怎么写了,菜呀
好了我的思路就夭折了= =。
于是看了题解发现可以从性质入手
比如现在有10个数,c = 3,10 9 8 7 6 5 4 3 2 1
那么从下标(3,8)中需要剔除2个数,必然是3和4,也就是2C的情况,但是如果变成C+C的情况,(3,5),(6,8)那么剔除的就是3和6,所以C的情况一定是最优的
也就是此时只主需要从i-c转移过来,而不需考虑(i-c-1)之后的
再只需与dp[i-1]+a[i]比较就行了
不需要dp[i-2] + a[i] + a[i-1] 是因为dp[i-2]+a[i-1]就是dp[i-1](我的理解)
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <set>
#include <cmath>
#define LL long long
#define INF 1000000000
#define mod 10056
const int maxn = 1e5 + 5;
using namespace std;
LL a[maxn];
LL dp[maxn];
LL sum[maxn];
LL q[maxn];
int main(){
int n,c;
scanf("%d%d",&n,&c);
for(int i=1; i<=n; i++){
scanf("%lld",&a[i]);
sum[i] = sum[i-1] + a[i];
}
int head = 1, tail = 0;
for(int i=1; i<=n; i++){
while(head <= tail && q[head] < i-c+1) head++;//不在范围内
while(head <= tail && a[q[tail]] > a[i]) tail--;//单调递增
q[++tail] = i;
dp[i] = dp[i-1] + a[i];
if(i >= c) dp[i] = min(dp[i], dp[i-c] + (sum[i] - sum[i-c] - a[q[head]]));
}
printf("%lld\n",dp[n]);
}