1. 程式人生 > >[四連測(三)]圓形穀倉

[四連測(三)]圓形穀倉

我複製了圖片,樣例什麼的就不要在意了。

解題思路

也許有人很迷惑,這個樣例怎麼算出來的,至少都是上百啊,我身邊是有人跟我這樣說過的,確實,題目描述有一點問題。它說的是“每頭奶牛都待在一個房間”,這句話是有歧義的。它沒說是都在“一個房間”,還是均勻地在“每一個房間”。這道題呢,是均勻地在每一個房間,運用一下貪心,模擬什麼的,絕對是可以弄出樣例的。

其實,這道題的實際就是貪心,但這道題的思維量其實挺大的,究竟怎麼樣移動才能夠花費最小呢?首先,一個很簡單很簡單的性質,每一個0,由它前面的最近的牛移動,如果多頭牛,則選擇移動最少的那頭牛,這樣肯定是最優的,不要問我怎麼找到的。這可以說是沒有問題的。可以說這就是我考試時的思路了,然而。。。你倒是算出來。。。模擬是真的複雜。

於是我們需要轉換思路。其實我們找好一個起點。能將每一頭牛排好序,移動就多好啊。是的,這樣容易多了。不用討論這麼多,一下就可以算出答案。

關鍵就是找起點了。找起點的話最好就要讓它這個位置奶牛數量為0或1,這樣它是不會動了。而這個環就要順時針向它來靠近。同時,移動後吧,我們需要留足夠的空位給後面的牛,避免要重複移動,那麼某一段中國房間數 - 奶牛數越大,這個起點其實越優,找到一個好的起點可以說是十分關鍵的,我們通過這樣的一個起點,許多的貪心思路就可以做出來了,雖然是次正解。

先看看如何找起點吧:

for (int i = 1;i <= n ;i ++ ){
        sum += a[i];
        if (sum - i < min_sum){
            min_sum = sum - i;
            pos = i;
        }
    }

這樣,我們找到了從1到pos,由於是一個環,其實從哪一個點開始都一樣的。保證這一段,空閒房間最多(在這一段奶牛同時也分別裝入房間中時)。這一段空閒房間最多,那麼從pos + 1 到 n這一段中,奶牛就最多,房間多半是不夠用的(除非原序列每一個房間都剛好一頭牛),我們就可以將奶牛往後移,可以說是搶吧,離pos順時針最近的奶牛最先移,移到空閒的房間。注意一點,我們依舊保證每一個房間都至少有一頭奶牛。

但其實,這裡就有一種貪心方法:pos順時針那一堆是要移動的是吧。我們每一個房間移到1為止,其實找到最近的一個房間,把它佔用了,其他牛就不能用,需跳過這一個房間,順時針找下一個沒被佔用的房間。這樣也可以保證和最小。這種貪心的思路實際上是並沒有問題的。那麼,也就有了我開始這樣的一種次正解

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<cstdlib>
#define N 100005
using namespace std;
int n , a[N * 2] , sum ,pos , min_sum = N * N , fg[N * 2] ;
long long ans;
int main(){
    scanf("%d",&n);
    for (int i = 1;i <=n ;i ++ ){
        scanf("%d",&a[i]);
        a[i + n ] = a[i];
    }
    for (int i = 1;i <= n ;i ++ ){
        sum += a[i];
        if (sum - i < min_sum){
            min_sum = sum - i;
            pos = i;
        }
    }
    for (int i = pos; i <= n + pos; i ++ ){
        while (a[i] > 1){
            int j = i + 1;
            while (fg[j] != 0){
                j ++ ;
            }
            a[i] -- ;
            a[j] ++ ;
            fg[j] = 1;
            ans += (j - i) * (j - i);
        }
    }
    printf("%lld",ans);
}

可以說是很神奇了,因為這樣的做法。。。3重迴圈,雖然不是n立方的時間複雜度,但看起來還是挺多的。耗時也應該挺久,其實我們想一想,房間總數只有10萬,那麼牛的總量亦只有10萬,其實細細一想,迴圈好像並不會耗費長的時間。這是一種近乎暴力的方法,事實證明,求對了起點這道題多好做啊。主要就是可以用大膽的貪心,算花費了。不過,運用這種方法,在四連測最後一次,那道比較類似的題,直接超時。

起點尋找的這個一個性質其實我是覺得比較難推的,也比較難想。但只有在這樣的兩段中,才存在一種性質,那便是起點的前面(或後面)一段的房間是肯定夠的,多半有剩餘,靠後面的牛補上來即可。

空閒的那一段,牛一般來說是分散的,這樣肯定不優,我們讓它們靠近起點,後面可能剩下的房間給擁擠的那一段。其實可以說是不好描述的,我覺得這道題貪心方法很多,做出來是這樣,解釋起來其實就有多種方法了。

這裡我們給每一頭牛嘗試編號,移動距離也就確定。花費就隨之算得出了。那如何弄呢。這裡我覺得還有點玄學。

找了新的起點pos,那麼pos後面的那一些數是可以很輕鬆地移動的,就如我做的性質那般。每一頭牛我們都按從1到n的順序編號,記住它的位置,利用pos,倒著遞減,那麼每一頭牛需移動到哪,我們便算了出來。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<cstdlib>
#define N 100005
using namespace std;
int n , a[N * 2] , sum ,pos , max_sum = -1,b[N * 2],len , c[N];
long long ans;
int main(){
    scanf("%d",&n);
    for (int i = 1;i <=n ;i ++ ){
        scanf("%d",&a[i]);
        int x = a[i];
        while (x){
            c[++len] = i;//求出這是第幾頭牛,並儲存它的位置。
            x -- ;
        }
    }
    for (int i = n;i >= 1 ;i --){//逆著找。可以知道,最大的sum - (n - i + 1)便是最優起點,因為可以證出,這個點前面那一段一定是最小的
        sum += a[i];
        if (sum - (n - i + 1) > max_sum){
            max_sum = sum - (n - i + 1);
            pos = i;
        }
    }
    b[pos] = max_sum + pos;
    int k = b[pos];
    for (int i = n;i >= 2 ;i -- ){//這裡倒著,處理每一頭牛將要移動到的點
        pos --;
        if (pos == 0)
            pos = n;
        k -- ;
        b[pos] = k;
    }
    for (int i = 1;i <= n ;i ++ ){//直接算出花費,因為每一頭牛需要移動到的位置已經可以算出來了。
        if (b[i] >= c[i])
            ans += (b[i] - c[i]) * (b[i] - c[i]);
        else
            ans += (b[i] + n - c[i]) * (b[i] + n - c[i]);
    }
    printf("%lld",ans);
}

總結

總覺得這道題思維量特別大,但實際理解的話,好像也沒什麼難的。考試的時候,想到無數總貪心方式,事實上都是合理的,但。。。你倒是可以算出花費來。程式碼複雜度是無法想象的,這道題有很多的性質,其實都可以弄出正解。但關鍵就要找出那一個是最合適的正解了。主要就要程式碼好打。