您的位置:首页 > 产品设计 > UI/UE

【HDU3415】Max Sum of Max-K-sub-sequence,思路+解题报告+AC代码+自虐般疯狂吐槽【0.3%达成!】

2012-08-03 02:56 465 查看
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <iostream>
using namespace std;
const int MAX_SIZE = 100010 * 2;
const int INFINITE = 99999999;
int num[MAX_SIZE];
int sum[MAX_SIZE];
int q[MAX_SIZE];
int ind[MAX_SIZE];
int begin;
int end;
/**
Problem: HDU3415-Max sum of max-k sequence 【0.3%达成!】
Thanks to : LPP学长,YL学姐,辛苦了两位= =开化我这种愚人
Reference:http://www.cppblog.com/wuxu/archive/2010/08/24/124575.aspx
Knowledge Point:单调队列
关于单调队列还有一篇好的Reference看,入门级的但是讲的足够详细了。 http://www.docin.com/p-49960245.html 用单调性优化优先队列。
Fucking Misunderstanding:
傻逼错误1:误把sum(i,j)写作了sum(i,j) = sum(i) - sum(j)!实际上这样就把元素j抛出去了,应该是sum(i)-sum(j-1)!
Thought:
【大半夜写的,发现bug欢迎fix。。。拿出证据,而不是“我觉得”三个字儿,谢谢】

好吧,都说过了是单调队列,递增的。
这里就当做对单调队列的一个大summary好了。
所谓单调队列,就是其中的内容是单调的,其中元素对应的数组下标也是单调的。
这里的单调指的不是单调上升就是单调下降。
单调队列的名字中虽然带了队列两个字,但是跟队列还是有区别的,不过也有共同之处。
共同之处就是在头(front)的位置取元素,由于单调性可以保证一类问题在单调队列的头处总有最优解,所以是可以这么做的。
不同之处就是单调队列的头部是可以删除元素的(我习惯用front++来实现),当然,跟队列一样,也可以从尾部添加、删除元素(但是头部不能添加元素哦!)
那么,这道题的解题思路在哪儿呢?
就是将给出数组的A[i]求和,Sum[i]表示从[0,i]区间数组A[]元素的和。
那么,如果求[i,j]区间的和,只要用sum[i] - sum[j-1]就好(因为如果减去的是sum[j]那么会把元素j也减掉。。妈的,误区!)

我们用一个单调队列将sum的序号存储在单调队列数组q[]中,并且保证rear - front <= K,这样的话我们保存的
就是连续个sum数组[]的下标,其中的值[ sum[front] , sum[rear-1] ]为sum[m] (  m属于区间[i,j], j - i <= K )
至于rear为什么要-1,则是因为rear始终指向空的空间用来存储sum的新值,记得么?队列的基本性质。
这样就好办了
我们可以将N个数存储在A[1]->A
中,A[0]为0,这样是为了方便计算sum[]
sum的计算是这样的:
for(int i = 1 ; i<= N ; i++)
sum[i] = sum[i-1] + A[i];
记得要把N个数的开头K个数(其实是K-1个……因为 N到N+K-1(包括两端)总共是K个数……虽然相减等于K-1,但是由于包括端点还得+1呢~)
放在A[N+1]到A[N+K]中(哎呀,偷懒,就这么放了,不影响结果!)
sum也要算到sum[N+K-1];

关键代码就在 ↓这里,恶心的地方也在这里
为什么是 sum[i-1] < sum[ q[rear-1] ]呢?sum[ q[rear-1] ]好解释
就是队内队尾元素的sum值嘛,但是为什么要sum[i-1]呢?
答案就是在底下的res = ( sum[i] - sum[ q[front] ] )里面。
而q[rear++]=i-1也提示了我们这一点。
只有在遍历到sum[i]的时候,q[rear]存储的是sum[i-1]的下标,才能根据当前的
sum[i]判断是否取到了最大值。证明见后
for(int i = 1 ; i <= N + K - 1 ; i++ )
{
while(front < rear && sum[i-1] < sum[ q[rear-1] ])
rear--;
q[rear++]=i-1;
if( sum[i] - sum[ q[front] ] > res )
{
begin = q[front] + 1;
end = i;
res = sum[i] - sum[q[front]];
}
}

证明刚才说的那个q[rear++] = i-1;

初始:
如果q[rear++]一直得到执行而从未发生rear--这种事(初始么!)
那么q[rear-1]存储的实际上是sum[i-2]的下标,判断sum[i-2] > sum[i-1]的话
rear--后退,让队尾元素变为sum[i-1]下标,亦即q[rear++] = i - 1,保持了q[]中sum值和下标的单调性。
维持:
rear--得到执行一次之后,rear指向的是sum[i-1]未更新前的最大元素,那么如果我们将sum[i-1]和sum[q[rear]]比较,尾部不断向前移动,就是存储下标对应的元素逐渐减小
也就是rear--,一直找到sum[i-1]比sum[q[rear-1]]要大的时候,我们才q[rear++] = i - 1;
终止:
根据一、二可以知道,这样是始终维护了队列的单调性的。

【耗时两个小时的证明,为毛就把i-1放在q[]而不是i呢?该部分为我一根筋的吐槽,非虐勿看!】
证明如下:
妈的,主要是这里想不明白真他妈讨厌!
想一下我们用单调队列来干毛啊,保证sum[q[front]]是这个区间内从A[1]到该区间开始端点和的最小值啊
这样我们sum[i] - sum[q[front]]才是最优啊。
如果你保存的是i的话,那么就得改成
sum[i]-sum[q[rear-1]]+num[q[rear-1]]是最优才可以。
那怎么改?
那就判断的话。。。
第一、不能跟sum[i]判断,要用(sum[q[rear-1]]-num[q[rear-1]])和sum[i]-num[i]判断
if( front < area && ( sum[i]-num[i] ) < (sum[q[rear-1]] - num[q[rear-1]])
本质上不还是跟放i-1是一样的么!!
sum[i]是用来确定最优解的,如果
sum[i] < sum[q[rear-1]] 判断的话,那么,那得到的是毛啊,你把用来确定最优解的东西用来判断?!
你得在优先队列里得到 i 点之前的 sum[k]( num[1] -> num[k]的和的最小值 ) 从而保证q[front]是最优的啊
这也就是为什么保证q[]里面是单调递增的原因啊,怎么能直接把 i 直接扔进去啊,到了 i 点应该是让 i 点之前的和最小啊
i点是用来转移的!看你自己的WA到死的代码,判断最优解的时候不也是
sum[i] - sum[q[front]] + num[q[front]]么,你把sum[i]用来转移的话,你就不能直接扔i进去啊
那不破坏了整个队列在[j,i-1] (j为起点,j = i - K)的最优状态么。。换言之那TM根本就不是最优状态

为神马破坏最优状态了呢?因为有 while(front < rear && i - q[front] > K ) front++ 这句话撑着啊,你要把sum[i]放进来是不是得丢出去一个
元素啊?而且还是front啊!你把最优的丢出去了啊!
其实也不是最优的,因为从初始化的时候就是错的=。=
总之,如果q[]放i并且还用sum[i]进行转移的话,那每次得到的都TM不是最优的状态,因为第一个元素(front)被扔出去了!

娘希匹的!Q.E.D!
【卧槽,我是不是精分了?】

第二、你的起始点确定累不死你。。。

**/
int solve(int N,int K)
{
int res = -INFINITE;
int front = 0;
int rear = 0;
for(int i = 1 ; i <= N+K-1  ; i++)
{
while(front < rear && i - q[front] > K )  //不用写等号!因为k 不等于0
front++; //保持距离在k
/**
主要就是下面那行代码让我费劲了,果然智商是硬伤
首先,简单的,q[rear-1],取得是队尾元素(rear始终指的是队列中的空地方)
而sum[i-1]的原因则是因为我们要用sum[i]来判断当前队列和是否是最优的!证明在解题报告中给出!
**/
while(front < rear && sum[i-1] < sum[ q[rear-1] ])
rear--;
q[rear++]=i-1;
if( sum[i] - sum[ q[front] ] > res )
{
begin = q[front] + 1;
end = i;
res = sum[i] - sum[q[front]];
}
}
if( end > N )
end = end - N;
return res;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("B:\\acm\\SummerVacation\\DP-II\\F.in","r",stdin);
freopen("B:\\acm\\SummerVacation\\DP-II\\F.out","w",stdout);
#endif
int T,N,K;
while(scanf("%d",&T) != EOF)
{
for(int t = 1 ; t <= T; t++)
{
scanf("%d%d",&N,&K);
num[0] = 0;
sum[0] = 0;
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
memset(q,0,sizeof(q));
memset(ind,0,sizeof(ind));
begin = 0; end = 0;
for(int i = 1 ; i <= N ;i++)
{
scanf("%d",&num[i]);
sum[i] = num[i] + sum[i-1];
}
for(int i = N+1; i <= N+K ; i++)
{
num[i] = num[i-N];
sum[i] = num[i]+sum[i-1];
}
int res = solve(N,K);
printf("%d %d %d\n",res,begin,end);
}
}
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: