1. 程式人生 > >bzoj4391: [Usaco2015 dec]High Card Low Card(貪心 + 思維)

bzoj4391: [Usaco2015 dec]High Card Low Card(貪心 + 思維)

原題連結

題目描述:奶牛Bessie和Elsie在玩一種卡牌遊戲。一共有2N張卡牌,點數分別為1到2N,每頭牛都會分到N張卡牌。
遊戲一共分為N輪,因為Bessie太聰明瞭,她甚至可以預測出每回合Elsie會出什麼牌。
每輪遊戲裡,兩頭牛分別出一張牌,點數大者獲勝。
同時,Bessie有一次機會選擇了某個時間點,從那個時候開始,每回合點數少者獲勝。
Bessie現在想知道,自己最多能獲勝多少輪?

輸入格式:第一行:一個整數N。
第2~N+1行:每行一個整數表述Elsie的卡。

輸出格式:一個整數表示答案。

輸入樣例
4
1
8
4
3

輸出樣例
3

解析:一眼看到這倒題好像是一個田忌賽馬,但肯定不能直接列舉改規則的時間點。
   於是考慮預處理。用pre[i]表示1~i用大牌獲勝的規則最多贏的輪數;suf[i]表示從i~n用小牌獲勝的輪數。
   關於pre和suf的預處理,可以用set來實現,每次從set中lower_bound進行尋找,之後刪除即可。
   最後的答案便是max{pre[i]+suf[i+1]}。
   關於這個貪心的正確性,證明也比較簡單。
   因為pre和suf中刪去的數總個數為n,若有一個元素重複,那麼必定有一個元素不被兩個刪去。
   設哪個元素為x,都被刪去的數為y,那麼分兩種情況討論。
   若x>y,那麼x可以在大的獲勝的規則中替換y。
   若x<y,那麼x可以在小的獲勝的規則中替換x。
   所以對答案不會造成影響。

程式碼如下:

#include<cstdio>
#include<set>
using namespace std;

const int maxn = 5e4 + 5;
int n, vis[maxn << 1], pre[maxn], suf[maxn], a[maxn], ans;
set <int> se1, se2;

int read(void) {
    char c; while (c = getchar(), c < '0' || c > '9'); int x = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; return x;
}

int main() {
    n = read();
      for (int i = 1; i <= n; ++ i) {
        a[i] = read(); vis[a[i]] = 1;
      }
      for (int i = 1; i <= n * 2; ++ i) 
        if (!vis[i]) se1.insert(i), se2.insert(-i);
      for (int i = 1; i <= n; ++ i) {
        set <int>::iterator it = se1.lower_bound(a[i]);
        if (it != se1.end()) pre[i] = pre[i - 1] + 1, se1.erase(it);
        else pre[i] = pre[i - 1]; 
      }
      for (int i = n; i ; -- i) {
        set <int>::iterator it = se2.lower_bound(-a[i]);
        if (it != se2.end()) suf[i] = suf[i + 1] + 1, se2.erase(it);
        else suf[i] = suf[i + 1];
      }
      for (int i = 0; i <= n; ++ i) ans = max(ans, pre[i] + suf[i + 1]);
    printf("%d", ans);
    return 0;
}