1. 程式人生 > 其它 >洛谷P7078 [CSP-S2020] 貪吃蛇 題解

洛谷P7078 [CSP-S2020] 貪吃蛇 題解

比賽裡能做出這題的人真的非常厲害,至少他的智商和蛇一樣足夠聰明。

首先有一個結論:

當前最強的蛇吃了最弱的蛇之後,如果沒有變成最弱的蛇,他一定會選擇吃!

證明:

假設當前最強的蛇叫石老闆。

如果下一條最強的蛇如果依舊是石老闆,那肯定不吃白不吃;

如果下一條最強蛇不是石老闆,此時最強的蛇沒有原先強,最弱的蛇也沒原先弱,吃掉後肯定比石老闆要弱。也就是說,當前最強的蛇吃了之後,如果會死,也會死在石老闆前面。那麼這樣一來,這條蛇會想盡辦法不死,從而石老闆也一定能不死。

有了這個結論,一部分蛇可以放心大膽地吃了,但是問題來了,如果吃了之後變成最弱的蛇了,到底選擇吃不吃呢?

稍微往後推一推就明白了:

當前最強蛇記為石老闆,下一條最強蛇記為喵老闆。石老闆進食後變成最弱的蛇了,如果喵老闆進食後不是最弱的蛇,他就會選擇吃(根據開頭的結論),這樣石老闆就涼了,所以石老闆當初的選擇一定是不吃。

如果喵老闆進食後依舊是最弱的蛇,那就會考慮下一條最強蛇的情況,起名為汪老闆。同樣分兩種情況:如果汪老闆進食後不是最弱的蛇,那他就會選擇吃,這樣喵老闆就涼了,所以他當初會選擇不吃,這樣石老闆就不會死,那麼石老闆當初就會選擇吃。如果汪老闆進食後變成了最弱的蛇,那就再考慮下一條蛇………………

這個問題就變成了一個遞迴的問題了,直到某條蛇吃了之後不是最弱的蛇或者只能下兩條蛇為止。這樣,最後一條蛇會選擇吃,倒數第二條蛇為了保命會選擇不吃,倒數第三條蛇可以放心大膽的吃,倒數第四條蛇會保命選擇不吃,倒數第五條蛇可以放心吃………………

這樣,石老闆選擇吃不吃,就和最後一條蛇之間的奇偶性相關了。並且石老闆選擇不吃,遊戲結束,石老闆選擇吃,遊戲也會在下一輪結束(因為喵老闆會選擇不吃)。

到目前為止,這個題目很清晰了,只需模擬兩個階段即可:

階段一:所有最強蛇進食後都不是最弱蛇,放心大膽吃!

階段二:所有最強蛇進食後都是最弱蛇,直到有條蛇可以放心吃為止(吃了後不是最弱或者只剩兩條)

階段一結束時,遊戲就基本結束了(根據階段二的奇偶性看能不能再吃一次)

利用set可以方便地維護最強、最弱蛇,時間複雜度\(O(Tnlogn)\),只能拿 70 分,程式碼如下:

int a[N];
int main() {
    int _;
    scanf("%d", &_);
    int n;
    for (int cas = 1; cas <= _; cas++) {
        if (cas == 1) {
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) {
                scanf("%d", &a[i]);
            }
        } else {
            int k;
            scanf("%d", &k);
            while (k--) {
                int x, y;
                scanf("%d%d", &x, &y);
                a[x] = y;
            }
        }
        set<pair<int, int> > se;
        for (int i = 1; i <= n; i++) {
            se.insert({a[i], i});
        }
        int flag = 0, ans;
        while (1) {
            if (se.size() == 2) {
                se.erase(se.begin());
                if (flag) {
                    if ((flag - se.size()) % 2) {
                        ans = flag + 1;
                    } else {
                        ans = flag;
                    }
                } else
                    ans = 1;
                break;
            }
            set<pair<int, int> >::iterator it = se.end();
            it--;
            int x = it->first, id = it->second;
            int y = se.begin()->first;
            se.erase(it);
            se.erase(se.begin());
            se.insert({x - y, id});
            if (se.begin()->second != id) {
                if (flag) {
                    if ((flag - se.size()) % 2) {
                        ans = flag + 1;
                    } else {
                        ans = flag;
                    }
                    break;
                }
            } else {
                if (flag == 0) flag = se.size();
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

正解:用兩個雙端佇列\(q_1,q_2\)維護即可。

先把初始的有序蛇放進\(q_1\)裡,此時\(q_1\)已滿足單調性,頭部小,尾部大,我們後面會讓\(q_2\)也滿足這樣的單調性。

第一階段:
每次從\(q_1,q_2\)的尾部取出最強的蛇,從\(q_1\)頭部取出最弱的蛇,如果吃了以後是最弱的,那就進入第二階段,否則直接裝入\(q_2\)的頭部。

為什麼可以裝進\(q_2\)並且是\(q_2\)裡最弱的?其實證明最初的結論時已經解釋過了,後面進食的蛇肯定是越來越弱的,而且這個階段最弱的蛇一定在\(q_1\)中。

第二階段:
此時最弱的蛇,就沒必要丟進佇列裡了,單獨維護一下就好了,因為連續的一段進食後都是最弱的,直到總蛇數等於 22 或者進食後不是最弱為止,而最強的依舊從 \(q_1,q_2\)的尾部找。

這樣就不需要用其他帶\(log\)的資料結構維護了,時間複雜度\(O(Tn)\),程式碼如下:

int a[N];
int main() {
    int _;
    scanf("%d", &_);
    int n;
    for (int cas = 1; cas <= _; cas++) {
        if (cas == 1) {
            scanf("%d", &n);
            for (int i = 1; i <= n; i++) {
                scanf("%d", &a[i]);
            }
        } else {
            int k;
            scanf("%d", &k);
            while (k--) {
                int x, y;
                scanf("%d%d", &x, &y);
                a[x] = y;
            }
        }
        deque<pair<int, int> > q1, q2;
        for (int i = 1; i <= n; i++) {
            q1.push_back({a[i], i});
        }
        int ans;
        while (1) {
            if (q1.size() + q2.size() == 2) {
                ans = 1;
                break;
            }
            int x, id, y;
            y = q1.front().first, q1.pop_front();
            if (q2.empty() || !q1.empty() && q1.back() > q2.back()) {
                x = q1.back().first, id = q1.back().second, q1.pop_back();
            } else {
                x = q2.back().first, id = q2.back().second, q2.pop_back();
            }
            pair<int, int> now = make_pair(x - y, id);
            if (q1.empty() || q1.front() > now) {
                ans = q1.size() + q2.size() + 2;  // 不吃
                int cnt = 0;
                while (1) {
                    cnt++;
                    if (q1.size() + q2.size() + 1 == 2) {
                        if (cnt % 2 == 0) ans--;
                        break;
                    }
                    int x, id;
                    if (q2.empty() || !q1.empty() && q1.back() > q2.back()) {
                        x = q1.back().first, id = q1.back().second, q1.pop_back();
                    } else {
                        x = q2.back().first, id = q2.back().second, q2.pop_back();
                    }
                    now = {x - now.first, id};
                    if ((q1.empty() || now < q1.front()) && (q2.empty() || now < q2.front())) {
                        ;
                    } else {
                        if (cnt % 2 == 0) ans--;
                        break;
                    }
                }
                break;
            } else {
                q2.push_front(now);
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

轉自OMG_wc 的部落格