諾諾的隊列

諾諾的隊列

題目描述

諾諾表現、成績雙優,於是校長給她一筆money,讓她去外地玩玩。由於本地沒有飛機場,所以諾諾只能坐火車去咯。所以諾諾今天去火車站買票,卻看到了N多人在火車站裏啊,諾諾一陣頭暈。機靈的她突然發現,有N個人在隊伍裏(和上文的N毫無關係- -||),人們等得很無聊,於是他們開始轉來轉去,想在隊伍裏尋找自己的熟人。隊列中任意兩個人A和B,如果他們是相鄰或他們之間沒有人比A或B高,那麼他們是可以互相看得見的。
諾諾想計算出有多少對人可以互相看見,那麼你能幫幫諾諾嗎?

本題數據範圍:
40%的測試數據:N≤10000;
80%的測試數據:N≤100000;
100%的測試數據:N≤500000。

輸入格式 2116.in

輸入的第一行包含一個整數N (1≤N≤500 000),表示隊伍中共有N個人;
接下來的N行中,每行包含一個整數,表示人的高度,人的高度<1000000。

輸出格式 2116.out

輸出僅有一行,包含一個數S,表示隊伍中共有S對人可以互相看見。

輸入樣例 2116.in

7
2
4
1
2
2
5
1

輸出樣例 2116.out

10

         看到題目,我們可以先從暴力方法入手。我們需要枚舉i,j兩個人(j<i),對每個人往前看能夠看到的人進行計數即可。

    很顯然,這樣的時間複雜度是O(N^2),期望得分只有40。因此,需要一個更加高效的辦法。

    通過分析,可以發現,本題是具有某些特殊性質的。

    如果i前面的y>x且a[y]>a[x],那麼i一定看不到x。從中可以發現單調性——i所能看到的人,從左往右看,它們的身高一定是不上升的序列。

    如果a[j]>a[i],那麼j就擋住了i的視線,i一定看不到j左邊的人。因此,i最遠只能看到第一個高於它的人(從右往左數)。

    由於此處沒有窗口長度的限制,因此此處是維護一個單調棧,棧中元素是單調遞減的。

    因此,將a[i]入棧時,要將棧中小於a[i]的元素出棧,並且,這些出棧元素是i能夠看到的人,將其計入答案中。而且,第i個人頂多只能看到一個比它高的人,因此如果棧中存在比a[i]大的元素,計數加1。

    但是,我們還需要考慮,同一身高,可能有多個人,因此需要記錄元素的實際數量,因此計數時應爲ans+=st[top--].num,而不是ans++。

    而且,第i個人是可以看到棧中與它一樣高的人的。因此需要特殊處理:如果棧頂元素等於a[i],則要另加,ans+=st[top].num,且不新建元素,st[top].num++即可;否則將a[i]入棧,st[++top].num=1。

         代碼如下:

#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=500005;
int n;
long long a[MAXN];
long long q[MAXN],num[MAXN];
long long ans,len,head,tail;
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)    cin>>a[i];
    head=1,tail=1;
    q[1]=a[1];
    num[1]=1;
    for(int i=2;i<=n;i++)
    {
       while(head<=tail&&q[tail]<a[i])
       {
           ans+=num[tail];
           tail--;
       }
       if(q[tail]==a[i])
       {
           ans+=num[tail];
           num[tail]++;
           if(tail>1) ans++;
       }
       else
       {
           if(tail>0) ans++;
           q[++tail]=a[i];
           num[tail]=1;
       }
    }
    cout<<ans<<endl;
    return 0;
}

 

         值得一提的是,這道題我改了很久才通過,一開始我的主程序是這樣的:

    for(int i=2;i<=n;i++)

    {

       while(head<=tail&&q[tail]<=a[i])

       {

           ans+=num[tail];

           tail--;

       }

       if(tail>0) ans++;

       if(q[tail+1]==a[i])//有相同的,被刪了。

       {

           tail++;

           num[tail]++;

       }

       else

       {

           q[++tail]=a[i];

           num[tail]=1;

       }     

     (寫法與標程有點不同,現將小於等於的都出棧,再將相等的入棧)

    這個程序只有50分。爲什麼?正是棕色的這句話出現了問題。q[tail+1]可能並不是這次出棧的可能解,可能是之前就出棧的無用解,如果它正好等於a[i],它反倒成了可能解,就會造成出錯。

    這個問題也很容易解決,只要將無用解的信息完全清空即可。(就是在while(head<=tail&&q[tail]<=a[i])循環中加了一句if(q[tail]<a[i])   q[tail]=0就AC了)。感謝lkb同學的熱心幫助!

    它給我敲了一個大大的警鐘——無用的信息,可能會造成困擾,一定要清空,才能避免錯誤!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章