*【POJ - 2796】 Feel Good (前缀和优化+单调栈维护)
題干:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Feel Good
| Time Limit:?3000MS | ? | Memory Limit:?65536K |
| Total Submissions:?12409 | ? | Accepted:?3484 |
| Case Time Limit:?1000MS | ? | Special Judge |
Description
Bill is developing a new mathematical theory for human emotions. His recent investigations are dedicated to studying how good or bad days influent people's memories about some period of life.?
A new idea Bill has recently developed assigns a non-negative integer value to each day of human life.?
Bill calls this value the emotional value of the day. The greater the emotional value is, the better the daywas. Bill suggests that the value of some period of human life is proportional to the sum of the emotional values of the days in the given period, multiplied by the smallest emotional value of the day in it. This schema reflects that good on average period can be greatly spoiled by one very bad day.?
Now Bill is planning to investigate his own life and find the period of his life that had the greatest value. Help him to do so.
Input
The first line of the input contains n - the number of days of Bill's life he is planning to investigate(1 <= n <= 100 000). The rest of the file contains n integer numbers a1, a2, ... an ranging from 0 to 10?6?- the emotional values of the days. Numbers are separated by spaces and/or line breaks.
Output
Print the greatest value of some period of Bill's life in the first line. And on the second line print two numbers l and r such that the period from l-th to r-th day of Bill's life(inclusive) has the greatest possible value. If there are multiple periods with the greatest possible value,then print any one of them.
Sample Input
6 3 1 6 4 5 2Sample Output
60 3 5解題報(bào)告:
? ? ?這個(gè)題干是真,的,惡,心。一會(huì)最大一會(huì)最小,搞這么復(fù)雜弄的題目看半天。。不過抽出核心部分就是一句話,求一個(gè)給定區(qū)間的 ? ?區(qū)間和與該區(qū)間最小值的 ?最大值。這題與HDU-1506那道Largest Rectangle(求最大矩形面積)差不多。
那道題的思路是:反正得是個(gè)矩形 還需要最大面積,那上底邊一定和某一個(gè)高度相等吧?那我就把每個(gè)高度所能延伸到的最遠(yuǎn)距離(也就是矩形的長)求出來,最后"長"乘"高",里面肯定有我要的答案了啊。而求每一個(gè)高度對(duì)應(yīng)的"長"的過程,也就是單調(diào)棧的過程。
本題也是一樣,我就把每一個(gè)a[i] 都當(dāng)做最小值,左邊右邊分別看看最遠(yuǎn)能延伸哪(這一步用單調(diào)棧),然后乘起來放在maxx里,維護(hù)maxx不就好了嗎。
? ?另一個(gè)題解中的解釋我覺得不錯(cuò):
題意是給出一個(gè)序列,要求的是一個(gè)區(qū)間,這個(gè)區(qū)間的最小值乘以這個(gè)區(qū)間數(shù)字的和 是最大值。求這個(gè)最大值與這個(gè)區(qū)間。
即:給你n個(gè)數(shù),求一個(gè)?max,其中max=某個(gè)區(qū)間和*該區(qū)間的最小值。
ac代碼1:
#include<iostream> #include<cstdio> #include<algorithm> #include<stack> using namespace std;const int MAX = 1e6 + 5; int a[MAX], L[MAX], R[MAX], top; long long sum[MAX]; int n; int l,r; long long maxx; stack<int > sk; int main() {cin>>n;for(int i = 1; i<=n; i++) {scanf("%d",&a[i]);sum[i] = sum[i-1] + a[i];}//需要左側(cè)第一個(gè)比a[i]小的元素,所以 從左向右維護(hù)一個(gè)嚴(yán)格單調(diào)遞增棧 for(int i = 1; i<=n; i++) {while(!sk.empty() && a[sk.top() ] >= a[i]) sk.pop();if(sk.empty() ) L[i] = 0;else L[i] = sk.top();sk.push(i);}while(!sk.empty() ) sk.pop();//需要右側(cè)第一個(gè)比a[i]小的元素,所以 從右向左維護(hù)一個(gè)嚴(yán)格單調(diào)遞增棧 for(int i = n; i>=1; i--) {while(!sk.empty() && a[sk.top() ] >= a[i]) sk.pop();if(sk.empty() ) R[i] = n+1;else R[i] = sk.top();sk.push(i);}l=r=1;long long tmp;for(int i = 1; i<=n; i++) {tmp=sum[R[i] - 1 ] - sum[L[i] ];tmp*=a[i];if(tmp>maxx) {l=L[i]+1;r=R[i]-1;maxx=tmp;} } // printf("%d %d\n",L[4],R[4]);printf("%lld\n",maxx);printf("%d %d\n",l,r);return 0 ;}初始化的細(xì)節(jié)!!!!!
注意上面這個(gè)代碼中,末尾的l=r=1是有講頭的,此處賦值成別的值就wa了。原因如下:(感謝wjh巨巨的分享)
? ? ?要么maxx賦初始值-1 ? 要么寫tmp>=maxx (這兩種情況的l和r都不需要賦初始值)。
? ? ?如果你按ac代碼中這樣寫,那就只能l=r=1才可以!! 因?yàn)檫@題卡了極限樣例需要特判一下:這樣輸入:1\n0\n ?或者1\n3\n你會(huì)發(fā)現(xiàn)輸出都是你賦值的l
別的ac代碼:(維護(hù)單調(diào)遞減棧?也可以做?)(好像還是遞增棧、、、)
實(shí)際上這個(gè)題目就是要對(duì)每一個(gè)節(jié)點(diǎn)進(jìn)行擴(kuò)展,這樣擴(kuò)展的話,復(fù)雜度是O(n^2)。減少時(shí)間復(fù)雜度要用單調(diào)棧,單調(diào)棧處理的問題就是對(duì)每一個(gè)節(jié)點(diǎn)進(jìn)行擴(kuò)展的問題,這個(gè)題目要維護(hù)的是一個(gè)單調(diào)遞減棧,即從棧頂元素到棧底元素,值是單調(diào)遞減的,即棧頂元素的值始終是棧的最大值。然后每一個(gè)值有屬于自己的區(qū)間,這個(gè)區(qū)間目的是為了記錄之后的元素向前延伸的用處。
向后延伸就靠從1到n掃描元素,(維護(hù)單調(diào)遞減棧)這樣當(dāng)掃描的元素大于棧頂元素時(shí),直接入棧。
當(dāng)掃描的元素等于棧頂元素時(shí),不記錄,只將區(qū)間延伸到后面。
當(dāng)掃描的元素小于棧頂元素時(shí),這時(shí)要計(jì)算棧內(nèi)當(dāng)前的值。因?yàn)閽呙璧脑貢r(shí)小于棧頂元素的,要求的是一個(gè)區(qū)間的最小值,所以棧內(nèi)那些大于該元素的值你會(huì)發(fā)現(xiàn)沒有用處了,只需要將它們的那些區(qū)間留下來就對(duì)了,這就是向前擴(kuò)展。
拿題目的sample舉例子:
3 1 6 4 5 2
?
一開始每一個(gè)數(shù)都有自己的區(qū)間:
3(1,1) ?1(2,2) ?6(3,3) ?4(4,4) ?5(5,5) ?2(6,6) ?-1(7,7)后面加一個(gè)最小值,為了最后計(jì)算棧內(nèi)元素使用。
先是3入棧。棧內(nèi)元素 3(1,1)
1<3,首先計(jì)算一下棧內(nèi)元素的值,記錄下來。然后要把棧內(nèi)大于1的全部彈出來,但是把它們的區(qū)間留下,棧內(nèi)就變成了1(1,2)。實(shí)際上此時(shí)就會(huì)知道(1,2)這段區(qū)間之內(nèi)的最小值是1。
6>1,直接入棧,棧內(nèi)元素變?yōu)?(1,2),6(3,3)。
4<6,將6彈出,彈出之前計(jì)算值。然后棧內(nèi)就變?yōu)?(1,2),4(3,4)。
5>4,直接入棧。棧內(nèi)元素是1(1,2),4(3,4),5(5,5)。會(huì)發(fā)現(xiàn)因?yàn)?沒有辦法向前擴(kuò)展了所以會(huì)知道5只能夠在(5,5)的區(qū)間內(nèi)最小,所以說站內(nèi)元素是在自己區(qū)間的左端點(diǎn)與棧頂元素的右端點(diǎn),這段區(qū)間之內(nèi)滿足著最小值的關(guān)系。1是在(1,5)這段區(qū)間內(nèi)最小,4是在(3,5)這段區(qū)間內(nèi)最小。這些值都會(huì)在碰到掃描的元素小于該元素時(shí)計(jì)算,記錄下來,就是這樣單調(diào)棧完成了對(duì)每一個(gè)元素進(jìn)行左右擴(kuò)展的目的。
2<5,2<4。要把5(5,5) 4(3,4)分別彈出,它們走之前要計(jì)算各自區(qū)間的值。
最后是-1,目的就是要將棧內(nèi)所有元素彈出,計(jì)算每一個(gè)元素左右擴(kuò)展的值。
#include <functional> #include <algorithm> #include <iostream> #include <fstream> #include <cstring> #include <cstdio> #include <cmath> #include <cstdlib> #include <queue> #include <stack> #include <map> #include <bitset> #include <set> #include <vector>using namespace std;const double pi = acos(-1.0); const int inf = 0x3f3f3f3f; const double eps = 1e-15; typedef long long LL; typedef pair <int, int> PLL;static const int N = 100100; stack <PLL> st; int val[N]; LL sum[N]; int l[N]; int r[N];int main() {int n;while (~scanf("%d", &n)) {sum[0] = 0;for (int i = 1; i <= n; ++i) {scanf("%d", &val[i]);sum[i] = sum[i - 1] + val[i];l[i] = r[i] = i;}while (!st.empty()) {st.pop();}for (int i = n; i >= 1; --i) {if (st.empty()) {st.push(make_pair(val[i], i));}else {while (!st.empty()) {PLL u = st.top();if (val[i] >= u.first) {break;}st.pop();l[u.second] = i + 1;}st.push(make_pair(val[i], i));}}while (!st.empty()) {PLL u = st.top();st.pop();l[u.second] = 1;}for (int i = 1; i <= n; ++i) {if (st.empty()) {st.push(make_pair(val[i], i));}else {while (!st.empty()) {PLL u = st.top();if (val[i] >= u.first) {break;}st.pop();r[u.second] = i - 1;}st.push(make_pair(val[i], i));}}while (!st.empty()) {PLL u = st.top();st.pop();r[u.second] = n;}LL ans = -1;int L, R;for (int i = 1; i <= n; ++i) {int y = r[i];int x = l[i];if (ans < (sum[y] - sum[x - 1]) * val[i]) {ans = (sum[y] - sum[x - 1]) * val[i];L = x;R = y;}}printf("%lld\n", ans);printf("%d %d\n", L, R);}return 0; }總結(jié):
? ? 至今不明白為什么要維護(hù)一個(gè)嚴(yán)格單調(diào)遞增棧。?
? ?答:因?yàn)槟阋氖且粋€(gè)嚴(yán)格小于你的元素,然后要取這兩個(gè)下標(biāo)中間的區(qū)域作為區(qū)間(因?yàn)橹虚g就都是比你大的或相等的了)。所以,其實(shí)使用單調(diào)遞增棧(以此舉例)的目的不是為了找第一個(gè)嚴(yán)格比你小的值(雖然具體實(shí)現(xiàn)是這樣的)而是為了找中間那部分都比你大的值的所在區(qū)間。嚴(yán)格單調(diào)遞增棧(實(shí)現(xiàn)語句為帶等號(hào)的 a[sk.top() ] >= a[i] ? -> ?then pop掉),找通過找嚴(yán)格小于他的第一個(gè)元素,目的是尋求大于等于他的那部分區(qū)間區(qū)域。這幾個(gè)邏輯關(guān)系要搞清楚!!
(但有的題 ? 用單調(diào)遞減棧目的就是很單純的找第一個(gè)比他高的元素,eg HDU 5033)稍后貼博客
與滑窗算法做區(qū)別?
滑動(dòng)窗口的精髓是在一個(gè)數(shù)列里保存著一個(gè)遞增數(shù)列,同時(shí)這個(gè)數(shù)列維持了它在原數(shù)列的位次遞增,這個(gè)窗口里保存的第一個(gè)數(shù)即在這個(gè)區(qū)間里最小的數(shù)。這樣不停得把新輸入的數(shù)同這個(gè)滑動(dòng)窗口里右邊的數(shù)比較,如果比它大,刪除窗口里的這個(gè)數(shù),同時(shí)刪除的數(shù)統(tǒng)治區(qū)域的最右邊就是新輸入的數(shù),它的左統(tǒng)治區(qū)域即新輸入的數(shù)的左統(tǒng)治區(qū)域,然后不停地向窗口的左邊比。這樣只需要一個(gè)數(shù)組記錄它左邊區(qū)域的邊界就可以了,? 右邊同理..
附一個(gè)滑窗算法典型題目 ? ??【POJ - 2823】 Sliding Window
題解:https://blog.csdn.net/qq_41289920/article/details/81071905
總結(jié)
以上是生活随笔為你收集整理的*【POJ - 2796】 Feel Good (前缀和优化+单调栈维护)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: register.exe - regis
- 下一篇: 【hihocoder - offer编程