题目链接:http://soj.me/1800
题目大意:给出一个序列a1, a2, a3,……an,求它的一个子序列使的这个子序列的和最小,并且这个子序列的长度只能在L和U之间。
对于这类子序列求和问题问法真是太多了,如果子序列长度没有上下界的话可以用动态规划来做,不过有上下界的时候,貌似没有想到动态规划的算法。
本题的思路大致如下:
首先求的整个序列的前缀和数组preSum[], 那么S[i, j]就可以表示为preSum[j]-preSum[i-1],那么本题需要求子序列最小和就可以通过从左到右扫描的方式来完成,对于当前位置i,只需要寻找到区间[max(0, i-U), i-L]的前缀和的最大值,那么 preSum[i]-preSum[j] (max(0, i-U)<=j <= i-L)就可以得到一个最小值,这样扫描完就得到了答案。
在寻找区间[max(0, i-U), i-L]的最大值的过程其实就是RMQ的过程,可以通过线段树来解决。
#include <cstdio>
#include <cstdlib>
#define MAX 32770
#define max(a, b)(a > b ? a : b)
int preSum[MAX];//对前缀和建立线段树
struct Node
{
int left, right, mPSum;
}tNode[4*MAX];
void build(int left, int right, int root)
{
tNode[root].left = left, tNode[root].right = right;
if(left == right)
{
tNode[root].mPSum = preSum[left];
return;
}
int mid = (left+right)>>1;
build(left, mid, root<<1);
build(mid+1, right, (root<<1) | 1);
tNode[root].mPSum = max(tNode[root<<1].mPSum, tNode[(root<<1)|1].mPSum);
}
int query(int left, int right, int root)
{
if(tNode[root].left == left && tNode[root].right == right)
return tNode[root].mPSum;
int mid = (tNode[root].left + tNode[root].right)>>1;
if(right <= mid)
return query(left, right, root<<1);
else if(left > mid)
return query(left, right, (root<<1) | 1);
else
return max(query(left, mid, root<<1), query(mid+1, right, (root<<1) | 1));
}
int main()
{
int n, L, U, val;
while(scanf("%d", &n), n)
{
scanf("%d %d", &L, &U);
preSum[0] = 0;
for(int i=1; i<=n; i++)
{
scanf("%d", &val);
preSum[i] = val + preSum[i-1];
}
build(0, n, 1);
int ans = INT_MAX;
for(int i=L; i<=n; i++)
{
int tmp = query(max(0, i-U), i-L, 1);
if(preSum[i] - tmp < ans)//更新最小值
ans = preSum[i] - tmp;
}
printf("%d\n", ans);
}
return 0;
}