1. 程式人生 > >ICPC 2018北京賽站網路賽題目4 : 80 Days+迴圈陣列動態維護字首和

ICPC 2018北京賽站網路賽題目4 : 80 Days+迴圈陣列動態維護字首和

題目連結:http://hihocoder.com/problemset/problem/1831 題目大意: 世界各地的一個圓圈上有n個城市,它們按照圓圈上的順序從1到n編號。當你到達這個城市會得到ai塊錢,從這個城市去另外的城市要花費bi塊錢,這個遊戲的目標是選擇一個城市作為起點,一開始你有c美元,然後沿著圓圈去一次訪問整個城市,最後回到起點。在旅途中,你的錢必須不低於零。 這裡有一個問題:要完成這次旅行,您會選擇哪個城市作為開始城市? 如果有多個答案,請輸出編號最小的答案。 如果沒有答案, 輸出-1

輸入的第一行是一個整數T(T ≤100),的測試案例數量。 對於每一個測試的情況下,第一行包含兩個整數N和C(1≤N ≤10^6, 0≤ C≤10^9)。 第二行包含N個整數ai(- 10^9 ≤ ai ≤10^9), 第三行包含N個整數bi…bn(0≤ bn ≤10^9)。

保證所有測試用例中n的總和小於10^6

思考: 在這裡插入圖片描述 因為到一個城市先得到錢,所以經過一個城市得到的錢為a[i]-b[i]; 圖中ai=a[i]-b[i];

首先求字首和,判斷s[n]+m>=0,如果成立則可以訪問全部城市。 這時列舉起始城市,如果以1為開始城市,則是s[i]+m,…,s[n]+m都要>=0。 如果以2為開始城市,則以2為起始城市的n個城市的字首和都要>=0。 … 以此類推

如果每列舉一個起點,求一次字首和,複雜度為O(T*n^n),肯定會T。

那麼我們可以動態維護字首和。 假如以2為起點時,除a1的字首和,其他的字首和都-=a1。而s[1]=(a[1]-b[1])+s[n-1]; 假如以3為起點時,除a2的字首和,其他的字首和都在以2為起點的基礎上-=a2。而s[2]=(a[2]-b[2])+s[1]; … 以此類推

那麼所有的-=可以用s3記錄下來,那麼每次只要維護s[i-1]就行了。 用multiset儲存字首和,那麼每次找min字首和的複雜度為log n。 整體的複雜度為O(T* n*log n)

思考:當時T了很多次,後來發現是陣列開小了。。。。。,怎麼不是MLE。。。。 話說當時還想到一種O(n)的思路,不過沒有驗證過是否正確。

記錄第一次的字首和的最小值S(min),和下標j,在每次列舉起點i時,除S[i-1]外,所有的字首和都減去相同的數,所以S(min)=min( S(min), S[i-1] )

如果S[min]的下標為j, 則所以的列舉i<=j都沒使S[min]-s3+m>=0, 則起點為j+1(沒有證明)

剛才再看了當時的程式碼,發現維護字首和的地方,維護的是S[i]的字首和,竟然AC。。。。。資料太水???

#include<bits/stdc++.h>
using namespace std;

int a[1000005];
int b[1000005];
long long s[1000005];


int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        multiset<long long> st;
        int n, m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=0;i<n;i++)
        {
            scanf("%d",&b[i]);
        }
        s[0]=a[0]-b[0];
        long long s1=s[0];
        for(int i=0;i<n;i++)
        {
            s[i]=s[i-1]+a[i]-b[i];//字首和
            st.insert(s[i]);
        }
        s1=s[n-1]+m;
        if(s1>=0)//如果成立則可以訪問全部城市。
        {
            long long s2=*st.begin();//字首和的最小值
            long long s3=0;
            if(s2+m>=0)              //第一個城市
            {
                printf("1\n");
            }
            else
            {
                for(int i=1;i<n;i++)
                {
                    s3+=(a[i-1]-b[i-1]);//記錄s3
                    st.erase(st.find(s[i-1]));
                    if(i==1)
                    {
                        s[0]=(a[0]-b[0])+s[n-1];//i="2"特殊維護
                        st.insert(s[0]);
                    }
                    else
                    {
                        s[i-1]=(a[i-1]-b[i-1])+s[i-2];//維護字首和
                        st.insert(s[i-1]);
                        /*
                        寫成
                        s[i]=(a[i]-b[i])+s[i-1];
                        st.insert(s[i]);
                        也能AC
                        資料比較水???
                        */
                    }

                    s2=*st.begin();
                    if(s2+m>=s3)//判斷是否滿足條件
                    {
                        printf("%d\n",i+1);
                        break;
                    }
                }
            }
        }
        else
            printf("-1\n");
    }

    return 0;
}