1. 程式人生 > >1619 Feel Good(高效演算法:滑動視窗)

1619 Feel Good(高效演算法:滑動視窗)

    題意大致是從一個數列裡找一個最短的子序列,使得這個子序列的最小值乘這個子序列的和最大。

    這道題最關鍵的地方是找到數列中的某個數在哪個連續序列裡是最小的,即這個數的統治區域,一開始我想到的就是輸入完之後重新遍歷數列,向前向後遍歷找第一個比它大的,但是總是感覺會超時,於是我想到了另一種方法,滑動視窗,就可以在輸入資料的時候同時找到這個數的的統治區域,即僅僅遍歷一遍陣列,分析如下:

    滑動視窗的精髓是在一個數列裡儲存著一個遞增數列,同時這個數列維持了它在原數列的位次遞增,這個窗口裡儲存的第一個數即在這個區間裡最小的數。這樣不停得把新輸入的數同這個滑動窗口裡右邊的數比較,如果比它大,刪除窗口裡的這個數,同時刪除的數統治區域的最右邊就是新輸入的數,它的左統治區域即新輸入的數的左統治區域,然後不停地向視窗的左邊比。這樣只需要一個數組記錄它左邊區域的邊界就可以了,而它的右邊界即是在滑動數組裡刪除的時候。

    我語文學得不好,表述可能不太清楚,我把樣例用圖解釋一下:


    差不多就是這樣。最後說一件事,我看到另一個人寫的程式碼,就是一開始那個分別向兩頭遍歷找邊界的方法,其實速度不比這個慢多少,可能是我的程式碼優化的問題,大家有興趣可以自己寫一下。下面給出我寫的程式碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1001000;
int A[maxn],T,m,n,f[maxn],q[maxn][2];
long long B[maxn],sum;
int main(){
    bool qwe = false;
    while (scanf("%d",&T) != EOF){
        B[0] = 0;sum = -1;
        q[0][0] = 100000000;q[0][1] = 1;
        f[1] = 1;
        int rear = 0;
        for (int i  = 1 ;i <= T;++i){
            scanf("%d",&A[i]);
            B[i] = B[i-1] + A[i];
            while (rear >= 0 && A[i] <= q[rear][0]) {
                int k = q[rear][1];
                long long s = (B[i-1] - B[f[k]-1]) * A[k];
                if (s > sum || (s == sum && fabs(n-m) > i-1-f[k])){
                    sum = s;
                    m = f[k];
                    n = i-1;
                }
                f[i] = f[k];
                rear--;
            }
            if (rear >= 0) f[i] = q[rear][1]+1;
            ++rear;
            q[rear][0] = A[i];
            q[rear][1] = i;
        }
        while (rear >= 0){
            int k = q[rear][1];
            long long s= (B[T] - B[f[k]-1]) * A[k];
            if (s > sum || (s == sum && fabs(n-m) > T-f[k])){
                sum = s;
                m = f[k];
                n = T;
            }
            rear--;
        }
        if (qwe) printf("\n");
        else qwe = true;
        printf("%lld\n%d %d\n",sum,m,n);
    }
    return 0;
}