1. 程式人生 > >考試(一)(標題未取好)

考試(一)(標題未取好)

目錄

 

思路篇

猴子跳樹

非正解思路——暴搜+剪枝

可能性正解思路——貪心+單調佇列優化

正解思路——DP+單調佇列優化

電話線路

非正解思路——暴搜

正解思路篇

一.引入:正解之——DP

二.優化:正解之——變數充當單調佇列

三.核心:正解之——變數的優化與迴圈方向的重要

四.點綴:正解之——滾動陣列優化時間和空間+卡常數

程式碼篇

猴子跳樹

貪心

DP+單調佇列

電話線路


思路篇

猴子跳樹

題目

有Q只猴子要從第一棵樹到第n棵樹去,第i只猴子一次跳躍的最遠距離為Ki。如果它在第x棵樹,那它最遠可以跳到第x+Ki棵樹。如果第j棵樹的高度比第i棵樹高或相等,那麼它從第i棵樹直接跳到第j棵樹,它的勞累值會增加1。所有猴子一開始在第一棵樹,請問每隻猴子要跳到第n棵樹花費的勞累值最小。

輸入

第一行一個整數n,表示有n棵樹。(2<=n<=1000000)

接下來第二行給出n個正整數D1,D2,……,Dn(1<=Di<=10^9),其中Di表示第i棵樹的高度。

第三行給出了一個整數Q(1<=Q<=25),接下來Q行,給出了每隻猴子一次跳躍的最遠距離Ki(1<=Ki<=N-1)。

輸出

輸出Q行,每行一個整數,表示一隻猴子的最小的勞累值。

樣例輸入

9

4 6 3 6 3 7 2 6 5

2

2

5

樣例輸出

2

1

非正解思路——暴搜+剪枝

主要看怎麼暴搜,暴搜得好,就可以多得一點(畢竟還有什麼剪枝,記憶化等等)

我就說一種大家都能想到的暴搜!

就是我們從當前的樹開始,往下從x+1到x+Ki都嘗試一遍(都暴搜一遍),一直到第n棵樹。

這個時間複雜度我算了算,每個點都要嘗試Ki次,還是Q個猴子,就要暴搜n次,就是O(n*sigma(Ki)),

這個最大的時候就是1000000*25000000=25000000000000 大概要3天左右!

嗯,你會說已經算過的點標記一下,後面就可以剪枝了,話說,你也減不了多少啊,也要個一兩天吧!

所以,這個暴搜肯定是不行的。

可能性正解思路——貪心+單調佇列優化

我就是用貪心做的,但後來聽說有人用貪心做出來了(我自然相信,因為我也相信能過的)

我們的貪心思想就是,後面有比當前這棵樹小的,就跳到比自己小的樹上,萬一有多個比自己小的樹呢?

那我們就跳較大的,這樣的話就可以為後面增加繼續往下跳的可能性。

那沒有比自己小的怎麼辦,那就只有跳最高的那個,增加勞累值啦,為什麼跳最高的,原因自然和上面是一樣的。

那怎麼找後面x+1到x+Ki有沒有比自己小的,怎麼找比自己小的較大的,或者找比自己大的最大的?

第一種 迴圈暴力

大家發現了吧,你上面的暴搜不也是每個結點要嘗試這麼多次嗎?這時間複雜度好像沒有什麼變化啊!

但是還是要快一點的,因為上面的暴搜是每個樹都要嘗試,而我們這裡只嘗試了要跳的樹,所以還是減了不少。

能減多少?可能還是要個半天一天才出來的到吧!

那這個貪心豈不沒有什麼卵用?就多騙了一點點分,一點點分也是分啊!但我們還可以優化!

第二種 單調佇列優化

我們何必去用迴圈呢?直接用單調佇列不就可以了?

什麼是單調佇列,大家自己去看吧,我就不說明了,反正用了單調佇列的話,就只用O(1)就可以找到較大的,直接代替迴圈

這樣的話,我們的時間複雜度就是O(nQ)

就是1000000*25=25000000 可是這個時間複雜度看起來只是很懸罷了,按理說不會超過1秒吧

1秒約等於100000000 我們這裡只有7位數,1秒有8位,這樣算就是250ms

但是很遺憾,我只騙了80%!!!震驚,所以我想說的是,大家以後啊,對於這個估算的時間複雜度,不要太過於相信

因為這個只是一個估計嘛,還有一些小操作我們是忽略了的,我上面那個只要再乘以4就暴了,那些小操作也應該要大於4吧!

所以你的估計要絕對的保守,因為你忽略了小操作,真實的時間複雜度肯定比這個高(除非你自己算錯了)

其實我可能是因為用的STL單調佇列,所以慢了幾倍!用手打還是能過的(說不定比正解還要快)

所以以後要用手打了啊!!!

正解思路——DP+單調佇列優化

我們很容易想到DP

我們設f[i]表示猴子跳到第i棵樹時的最小勞累值(當然這個你要做Q次)

那麼 f[i] = min(f[i],f[j] + (a[j]<=a[i])) j的範圍自然就是i-Ki到i-1了

很明顯,我們還是用單調佇列來優化,由於後面的最多隻會加上1,所以是不會影響的,因為就算要加1的話,最多跟第2小相等

這個自然就是滑動視窗的模板了,只是多了一個判斷,特殊時加上一個1

我看了看,跟上面的貪心單調佇列優化過的時間複雜度一樣的,哎,要用手打的啊!!!

電話線路

題目

最近,約翰的奶牛們越來越不滿足於牛棚裡一塌糊塗的電話服務,於是,她們要求
約翰把那些老舊的電話線換成效能更好的新電話線。新的電話線架設在己有的n根
電話線杆上,第i根電話線的高度為hi, ( 1 <= hi<= 100)。電話線總是從一根電話
線杆的頂端被弓}到相鄰的那根的頂端,如果這兩根電話線杆的高度hi和hj不同,那
麼約翰就必須支付c * |hi - hj|的費用,當然,你不能行動電話線杆,只能按照原有
的順序在相鄰杆間架設電話線。
加高某些電話線杆能減少架設電話線的總費用,儘管這項工作也需要支付一定的
費用。更準確的說,如果他把一根電話線杆加高x米的話,他需要付出x^2費用。
請你幫約翰計算一下,如果合理的進行這兩項工作,他最少要在這個電話線改造
工程中花多少錢。

輸入

第一行輸入兩個數n和c, 含義如上
接下來n行,每行一個整數hi

輸出

輸出約翰完成電話線改造工程需要花費的最小費用

樣例輸入

5 2

2

3

5

1

4

樣例輸出

15

非正解思路——暴搜

怎麼又是這個,我也沒有辦法啊!

我們暴搜那些需要增加

再暴搜增加的這些電話線杆要增加多少

突然感覺這個暴搜很無語啊!時間複雜度為O(n!maxhi!)

哎,這個要算到猴年馬月啊!據不完全估計,太陽死亡時都算不出來!

正解思路篇

一.引入:正解之——DP

我們設f[i][j]表示只算前i個電線杆且第i個電線杆增加後的高度為j的最小費用

則我們可以知道f[i][j] = min(f[i][j],f[i - 1][k] +|j-k|*c+(j-h[i])^2)

我們自然要迴圈i,j,k

j的範圍為h[i]到maxhi,即到最高電線杆的長度,因為你超過了這個值只會更虧的,沒有意義,而你又只能增加,最小肯定是原來的高度。

k代表的是第i-1跟電線杆的長度,自然是從h[i-1]到maxhi了,而我們的第一根電線杆自然是要單獨處理的(即預處理)

時間複雜度就是(n*maxhi*maxhi) 最壞時是100000*100*100=1000000000 這個就是10秒左右!肯定超時,所以我們要優化

二.優化:正解之——變數充當單調佇列

我們之前之所以要用單調佇列,是因為它是有一個區間限制的,所以我們用一個變數的話就顯得很只顧眼前,所以會出錯。

這裡我們的範圍就只有前面一個,你用單調對列的話,永遠就只會對當前的狀態貢獻,因為後面只要前面一個的,所以儲存了又要立馬出去,用單調佇列沒有必要,就用一個變數記錄好前面的最小值就行了。

那你會說,我們每次j一改變,又要去更新這個變數,不就差不多嘛,還是很浪費時間啊!

我們先不慌,來看一看怎麼變形

j>k

f[i][j] = min(f[i][j],f[i - 1][k] + (j-k)*c + (j-h[i])^2)

f[i][j] = min(f[i][j],f[i - 1][k] + j*c - k*c + (j-h[i])^2)

我們發現,不管K怎麼變,與j相關的都不變,即j*c + (j-h[i])^2 這一堆是不變的,所以我們只需要考慮f[i - 1][k] - k*c 最小就行了

變數記錄的就是這個中最小的 (就把這個變數記作min1吧)我們就得到 f[i][j]= min(f[i][j],min1 + j*c + (j-h[i])^2)

j<=k

f[i][j] = min(f[i][j],f[i - 1][k] + (k-j)*c + (j-h[i])^2)

f[i][j] = min(f[i][j],f[i - 1][k] + k*c - j*c + (j-h[i])^2)

記這個變數為min2 min2 = min(min2,f[i - 1][k] + k*c)

f[i][j] = min(f[i][j],min2 - j*c + (j-h[i])^2)

三.核心:正解之——變數的優化與迴圈方向的重要

如何求這兩個變數 用迴圈,那你的時間複雜度減了沒有多少啊!

我們發現,j>k時,如果我們的j是從小到大的,每次j增加1,其實我們的變數min1在上一次的基礎上,迴圈k的範圍只是多了上一輪的j而已,我們既然上一輪中,已經求出k到上一輪的j-1的最小值,那麼這一次,何必再用迴圈求呢?

直接比較現在的min1和k==上一輪的j(即這一輪的j-1)不就可以了嗎?

對於j<=k時 我們j從大到小

j每減少一個,min2的範圍也是隻從上一輪的階段的範圍多了個j

所以我們再也不用一個迴圈了,時間複雜度就降為O(n*maxhi) 100000*100=10000000 約為100ms

這裡我們就看出了好的迴圈方向使得優化的機率更大,也更節省時間。

四.點綴:正解之——滾動陣列優化時間和空間+卡常數

有些人說滾動陣列只會優化空間,但大量資料表明,一定程度上,也會優化時間的

由於我們的DP只用到了i-1的階段,所以我們不用開n*maxhi怎麼大的陣列

就只用開2*maxhi就行了,不停的滾動,來回利用(不懂的自己先了解再說)

卡常數就是inline reg 讀優寫優啦,不懂的自己去了解吧!

程式碼篇

建議三思而後行,思考1小時,除錯2小時以上再來看!

猴子跳樹

貪心

請注意,這個是用的STL,想要過的請自行改成手動版(不會自學)

#include <cstdio>
#include <list>
#define reg register
using namespace std;

struct node{
    int x, id;
};
int n, q, a[1000005], x, w, ans;
list<node> q1;
list<node> q2;
bool flag;

inline void read(int &x){
    int f = 1;
    x = 0;
    char s = getchar();
    while(s < '0' || s > '9'){
        if(s == '-'){
            f = -1;
        }
        s = getchar();
    }
    while(s >= '0' && s <= '9'){
        x = (x << 3) + (x << 1) + (s - '0');
        s = getchar();
    }
    x *= f;
}

int main(){
    freopen("monkey.in","r",stdin);
    freopen("monkey.out","w",stdout);
    read(n);
    for(reg int i = 1; i <= n; i++){
        read(a[i]);
    }
    scanf("%d",&q);
    for(reg int i = 1; i <= q; i++){
        read(x);
        w = 1;
        ans = 0;
        while(!q1.empty()){
            q1.pop_back();
        }
        while(!q2.empty()){
            q2.pop_back();
        }
        for(reg int j = 2; j <= n; j++){
            if(j - w > x){
                flag = 0;
                while(!q1.empty()){
                    node t = q1.front();
                    if(t.id <= w){
                        q1.pop_front();
                    }
                    else {
                        break;
                    }
                }
                while(!q2.empty()){
                    node t = q2.front();
                    if(t.id <= w){
                        q2.pop_front();
                    }
                    else {
                        break;
                    }
                }
                if(!q2.empty()){
                    node t = q2.front();
                    w = t.id;
                }
                else {
                    node t = q1.front();
                    w = t.id;
                    ans ++;
                    flag = 1;
                }
                while(!q1.empty()){
                    node t = q1.front();
                    if(t.id <= w){
                        q1.pop_front();
                    }
                    else {
                        break;
                    }
                }
                while(!q2.empty()){
                    node t = q2.front();
                    if(t.id <= w){
                        q2.pop_front();
                    }
                    else {
                        break;
                    }
                }
                if(flag){
                    while(!q1.empty()){
                        node t = q1.front();
                        q2.push_back(t);
                        q1.pop_front();
                    }
                }
            }
            node h;
            h.x = a[j];
            h.id = j;
            if(a[j] >= a[w]){
                while(!q1.empty()){
                    node t = q1.back();
                    if(t.x <= h.x){
                        q1.pop_back();
                    }
                    else {
                        break;
                    }
                }
                q1.push_back(h);
            }
            else {
                while(!q2.empty()){
                    node t = q2.back();
                    if(t.x <= h.x){
                        q2.pop_back();
                    }
                    else {
                        break;
                    }
                }
                q2.push_back(h);
            }
        }
        while(w != n){
            flag = 0;
            while(!q1.empty()){
                node t = q1.front();
                if(t.id <= w){
                    q1.pop_front();
                }
                else {
                    break;
                }
            }
            while(!q2.empty()){
                node t = q2.front();
                if(t.id <= w){
                    q2.pop_front();
                }
                else {
                    break;
                }
            }
            if(!q2.empty()){
                node t = q2.front();
                w = t.id;
            }
            else {
                node t = q1.front();
                w = t.id;
                ans ++;
                flag = 1;
            }
            while(!q1.empty()){
                node t = q1.front();
                if(t.id <= w){
                    q1.pop_front();
                }
                else {
                    break;
                }
            }
            while(!q2.empty()){
                node t = q2.front();
                if(t.id <= w){
                    q2.pop_front();
                }
                else {
                    break;
                }
            }
            if(flag){
                while(!q1.empty()){
                    node t = q1.front();
                    q2.push_back(t);
                    q1.pop_front();
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

DP+單調佇列

我發現用STL也會超時,這也說明了和貪心的時間複雜度是一樣的!

#include <cstdio>
#define reg register
 
inline void read(int &x){
    int f = 1;
    x = 0;
    char s = getchar();
    while(s < '0' || s > '9'){
        if(s == '-'){
            f = -1;
        }
        s = getchar();
    }
    while(s >= '0' && s <= '9'){
        x = (x << 3) + (x << 1) + (s - '0');
        s = getchar();
    }
    x *= f;
}
 
inline void write(int x){
    if(x < 0){
        putchar('-');
        x *= -1;
    }
    if(x > 9){
        write(x / 10);
    }
    putchar((x % 10) + '0');
}
 
struct node{
    int x, id;
};
int n, a[1000005], w, x, ans, head, tail;
node q[1000005];
 
int main(){
    read(n);
    for(reg int i = 1; i <= n; i++){
        read(a[i]);
    }
    read(w);
    for(reg int i = 1; i <= w; i++){
        read(x);
        ans = 0;
        head = tail = 1;
        for(reg int j = 1; j <= n; j++){
            node h;
            while(head < tail){
                if(j - q[head].id > x){
                    head ++;
                }
                else {
                    break;
                }
            }
            h.x = 0;
            h.id = j;
            if(j == 1){
                q[tail] = h;
                tail ++;
                continue;
            }
            h = q[head];
            if(a[h.id] <= a[j]){
                h.x ++;
            }
            ans = h.x;
            h.id = j;
            while(head < tail){
                node t = q[tail - 1];
                if(t.x > h.x || (t.x == h.x && a[t.id] <= a[h.id])){
                    tail --;
                }
                else {
                    break;
                }
            }
            q[tail] = h;
            tail ++;
        }
        write(ans);
        putchar('\n');
    }
    return 0;
}

電話線路

#include <cstdio>
#include <iostream>
#include <cstring>
#define reg register
using namespace std;
 
inline void read(int &x){
    int f = 1;
    x = 0;
    char s = getchar();
    while(s < '0' || s > '9'){
        if(s == '-'){
            f = -1;
        }
        s = getchar();
    }
    while(s >= '0' && s <= '9'){
        x = (x << 3) + (x << 1) + (s - '0');
        s = getchar();
    }
    x *= f;
}
 
inline void write(int x){
    if(x < 0){
        putchar('-');
        x *= -1;
    }
    if(x > 9){
        write(x / 10);
    }
    putchar((x % 10) + '0');
}
 
int n, c, a[100005], dp[2][105], iop, noip;
 
int main(){
    read(n);
    read(c);
    int maxhigh = 0;
    for(reg int i = 1; i <= n; i ++){
        read(a[i]);
        maxhigh = max(maxhigh, a[i]);
    }
    memset(dp, 0x3f, sizeof(dp));
    noip = 1;
    for(reg int i = a[1]; i <= maxhigh; i ++){
        dp[iop][i] = (i - a[1]) * (i - a[1]);
    }
    /*for(reg int i = 2; i <= n; i++){
        for(reg int j = a[i]; j <= maxhigh; j++){
            for(reg int k = a[i - 1]; k <= maxhigh; k++){
                dp[i][j] = min(dp[i][j], dp[i - 1][k] + (jdz(j - k)) * c + (j - a[i]) * (j - a[i]));
            }
        }
    }*/
    for(reg int i = 2; i <= n; i ++){
        int min1 = 2147483647, min2 = 2147483647;
        for(reg int j = 0; j < a[i]; j ++){
            min1 = min(min1, dp[iop][j] - c * j);
        }
        for(reg int j = a[i]; j <= maxhigh; j ++){
            dp[noip][j] = min(dp[noip][j], min1 + c * j + (j - a[i]) * (j - a[i]));
            min1 = min(min1, dp[iop][j] - c * j);
        }
        for(reg int j = maxhigh; j >= a[i]; j --){
            min2 = min(min2, dp[iop][j] + c * j);
            dp[noip][j] = min(dp[noip][j], min2 - c * j + (j - a[i]) * (j - a[i]));
        }
        for(reg int j = 0; j <= maxhigh; j++){
            dp[iop][j] = 0x3f3f3f3f;
        }
        swap(iop, noip);
    }
    int ans = 2147483647;
    for(reg int i = a[n]; i <= maxhigh; i++){
        ans = min(ans, dp[iop][i]);
    }
    write(ans);
    return 0;
}