[JOI 2013 Final]JOIOI 塔
題意
給出一個由 \(\text{JOI}\) 組成的字串,可從中取出一些子序列。
求最多取出多少 \(\text{IOI}\) 和 \(\text{JOI}\)。
思路
若答案 \(x\) 可行,則所有 \(y<x\) 均可行,
若答案 \(x\) 不可行,則所有 \(y>x\) 均不可行。
這樣就可以可行性二分。
考慮如何判斷答案 \(x\) 是否可行。
\(\text{JOI}\) 和 \(\text{IOI}\) 都有 \(\text{OI}\)。
發現 \(\text{J}\) 只能用來拼 \(\text{JOI}\) 的第一位,\(\text{O}\) 只能用來拼 \(\text{OI}\)。
而問題就在於 \(\text{I}\),既可以用來做 \(\text{IOI}\) 的第一位,也可以用來拼 \(\text{I}\)。
從後往前掃描字串,同時維護 \(\text{I,O,J,OI,JOI,IOI}\) 的個數。
如果掃到 \(\text{J,O}\),將對應的個數加一,如果可以就拼接成為 \(\text{JOI,OI}\)。
對於 \(\text{I}\),我們需要的 \(\text{OI}\) 只有 \(x\) 個,若當前拼出的 \(\text{OI}\) 總數小於 \(x\),就拼 \(\text{OI}\),否則和 \(\text{OI}\) 拼接出 \(\text{IOI}\)。
如果最後的 \(\text{JOI,IOI}\) 總數大於等於 \(x\) 則可行。
程式碼
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n;
char S[N];
bool check(int x) {
int JOI = 0, IOI = 0, OI = 0;
int J = 0, O = 0, I = 0;
for (int i = n; i >= 1; i --) {
if (S[i] == 'I') {
I ++;
if (OI + JOI + IOI + I - 1 >= x && OI > 0) {
I --;
OI --;
IOI ++;
}
}
if (S[i] == 'O') {
O ++;
if (I > 0 && O > 0) {
O --;
I --;
OI ++;
}
}
if (S[i] == 'J') {
J ++;
if (J > 0 && OI > 0) {
J --;
OI --;
JOI ++;
}
}
}
return JOI + IOI >= x;
}
int main() {
freopen("joi.in","r",stdin);
freopen("joi.out","w",stdout);
scanf("%d", &n);
scanf("%s", S + 1);
int l = 0, r = n, mid, res;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
res = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
cout << res << "\n";
return 0;
}