[JOI 2013 Final]JOIOI 塔

maniubi發表於2024-10-10

[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;
}