[SMOJ2088]魔法練習

40%:

暴力枚舉每一個 ? 是變成左括號還是右括號,再檢查合法性,取費用最小的合法方案。

時間複雜度:O(2m) ,其實我覺得對於 n100 ,很容易構造出 m 稍微大一點的數據卡掉這種暴力,但出題人居然沒有卡。

(反思:但我在考試的時候居然打錯了暴力,多組數據忘記清空,只得了 20 分)

60%:

如果做出了 1989“圓括號”一題,再結合上面的暴力,不難想到 DP 的方法。

f[i][j] 爲前 i 位決策完,左括號比右括號多 j 個的最小代價,那麼有

f[i][j]=min{f[i1][j1]+Aif[i1][j+1]+Bi

注意轉移的過程中要保證任意時刻左括號個數都必須多於或等於右括號個數。

對於已給定的括號,可以直接跳過(f[i][j]f[i1][j] ),也可以通過令 Ai=0,Bi= (當前固定爲左括號)或 Ai=,Bi=0 (當前固定爲左括號)的方法,使得給定的括號一定被選擇到。但是要注意有一個隱患,小心出現無窮大不夠大的情況。

時間複雜度:O(n2)

100%:

要保證滿足條件,決策位置和合法性都不能不考慮,上面的 DP 已是最優可能,但無法解決 105 這麼大規模。因此正解並不是對上面的 DP 再進行玄學優化。

考試的時候其實是有想到貪心的,但卻無從下手。正解就是貪心。

輸入的時候,先算出每個 ? 的 AiBi ,記爲 diffi

第一步,什麼都不考慮,把全部都變成右括號。當前費用爲 Bi

之後,從第 1 位開始,從左向右逐位考慮,將到目前爲止左括號個數與右括號個數的差值記爲 s

對於一個合法的括號序列,前 i 位中的左括號個數總是應該大於等於右括號個數,即 s 總是大於等於 0。一旦我們發現 s<0 的情況,說明要進行調整。

所謂調整,就是在 [1,i] 中選出一個右括號把它變成左括號,選誰好呢?不妨設選了第 j 位,那麼新的費用爲當前費用Bj+Aj ,即當前費用+diffj

爲了最小化費用,顯然要選一個使 diffj 儘可能小的 j 。這一步,就可以用數據結構去實現:線段樹,堆,優先隊列都可以。

另外,類似於上面的 DP,對於已給定的括號,如果直接跳過,要注意判斷可能出現的在前面選一個變成左括號的時候沒得選的局面。如果是對 AiBi 的值進行設定,則要最後判斷答案如果大於 ,應該輸出 -1。

時間複雜度:O(nlog2n)
正確性證明:暫略。
參考代碼:

#include <algorithm>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>

using namespace std;

const int MAXN = 1e5 + 100;

int c;
long long diff[MAXN];

int l;
char str[MAXN];

long long ans;

priority_queue <long long, vector<long long>, greater<long long> > pq;

void solve() {
    int k = 0, s = 0;
    while (!pq.empty()) pq.pop();
    for (int i = 0; i < l; i++) {
        if (str[i] == '(') ++s; else --s;
        if (str[i] == '?') pq.push(diff[k++]); //將來可以變成左括號
        if (s == -1) //不合法的情況,在前面選一個變成左括號 
            if (pq.empty()) { //沒得變的情況,不合法
                ans = -1;
                return;
            } else {
                ans += pq.top(); //否則選一個花費最小的變
                pq.pop(); s = 1;
            }
    }
    if (s) ans = -1; //到最後左括號比右括號多也不合法
}

int main(void) {
    freopen("2088.in", "r", stdin);
    freopen("2088.out", "w", stdout);
    while (~scanf("%s", str)) {
        c = 0; //一開始多組數據忘記清零,暴力都 WA 了
        l = strlen(str);
        for (int i = 0; i < l; i++) c += str[i] == '?';

        ans = 0LL;
        for (int i = 0; i < c; i++) {
            long long A, B; scanf("%lld%lld", &A, &B);
            diff[i] = A - B;
            ans += B; //先全部變成右括號
        }

        solve();
        printf("%lld\n", ans);
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章