【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; }
相关文章推荐
- 【HDU4313】Matrix 多校 解题报告+AC代码+思路+算法正确性证明,此为Kruskal贪心恶心版本,非自虐倾向慎入!建议想找解题报告的童鞋看简单版本的,这个我写给自己【目标达成 0.2%】
- 【DP+单调队列】 hdu3415 Max Sum of Max-K-sub-sequence
- HDU3415 - Max Sum of Max-K-sub-sequence - 单调队列
- hdu3415 Max Sum of Max-K-sub-sequence 单调队列
- 【hdu3415】【单调队列 】Max Sum of Max-K-sub-sequence【求长度不大于k的区间最大子串和】
- hdu3415 Max Sum of Max-K-sub-sequence
- 【HDU4313】 - Matrix - 树状DP Version 思路+解题报告+AC代码【0.4%达成】
- hdu3415 Max Sum of Max-K-sub-sequence
- hdu3415 Max Sum of Max-K-sub-sequence(单调队列求n个数中和的最大值)
- hdu3415 Max Sum of Max-K-sub-sequence 单调队列
- 【HDU4313】Matrix 多校 解题报告+AC代码+思路+算法正确性证明,此为Kruskal贪心简单版本,恶心版本稍后放出【目标达成 0.2%】
- hdu3415 Max Sum of Max-K-sub-sequence
- hdu3415 Max Sum of Max-K-sub-sequence
- HDU3415:Max Sum of Max-K-sub-sequence(单调队列)
- hdu3415 Max Sum of Max-K-sub-sequence
- HDU3415 Max Sum of Max-K-sub-sequence
- hdu3415 Max Sum of Max-K-sub-sequence 单调队列
- HDU3415:Max Sum of Max-K-sub-sequence(单调队列)
- HDU3415 Max Sum of Max-K-sub-sequence (DP+单调队列)
- HDU 3415 Max Sum of Max-K-sub-sequence(单调队列)