JOIOI の塔 题解
题目描述
有一长度为
N
N
N 的字符串,只由 J
,O
与 I
组成。现在,你可以按从前到后顺序选择三个字母,使他们组成的字符串为 JOI
或 IOI
,但是字母不可以被选择两次。你需要求出最多可以选取几个这样的字符串。
解析
我们发现答案符合单调性:如果可以拼出 n n n 个字符串,那一定可以拼出 n − 1 n-1 n−1 个字符串。我们利用这个进行二分,那我们只需判断是否可以拼出 m i d mid mid 个字符串即可。
由于 JOI
与 IOI
后缀相同,想到从后往前遍历字符串,将每个字符放在原有的字符串前面或者将 I
字符作为一个新的字符串,这样每个完整字符串的形成过程就是 I
->OI
->IOI
或 I
->OI
->JOI
。令
c
n
t
1
cnt1
cnt1 表示现成的 I
字符串个数,
c
n
t
2
cnt2
cnt2 表示现成的 OI
字符串个数,
c
n
t
3
cnt3
cnt3 表示现成的 IOI/JOI
字符串个数。于是,对于每个 J
,将一个 OI
变为 JOI
;对于每个 O
,将一个 I
变为 OI
;对于每个 I
,需要分开考虑:若目前的字符串个数(
c
n
t
1
+
c
n
t
2
+
c
n
t
3
cnt1+cnt2+cnt3
cnt1+cnt2+cnt3)已经达到了最终的需求(
m
i
d
mid
mid),则无需新建一个字符串 I
,如果没有到达,那就新建一个 I
的字符串。
例如:判断 JOIOII
能否拼出一个字符串。
JOIOI[I]
:{"I"}
;(目前字符串个数小于需求,所以新建)
JOIO[II]
:{"I"}
;(目前字符串个数达到需求,无需新建,那这个字符串就不需要了)
JOI[OII]
:{"OI"}
;(对于 O
只需将其加在 I
的前面)
JO[IOII]
:{"IOI"}
;(字符串个数到达需求,无需新建;目前已有 OI
字符串,所以加在它的前面就好了);已有的字符串个数达到需求,直接返回结果:JOIOI
可以拼出一个字符串。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
for(; ch >= '0' && ch <= '9'; s = 10 * s + ch - '0', ch = getchar());
return s * w;
}
const int MAXN = 1000005;
int N;
string s;
bool check(int x){
int tot = 0, one, two;
one = two = 0;
for(int i = N; i >= 1; i--){
if(s[i - 1] == 'J'){
if(two != 0) two--, tot++;
} else if (s[i - 1] == 'O'){
if(one != 0) one--, two++;
} else if (s[i - 1] == 'I'){
if(one + two + tot >= x && two){
two--, tot++;
} else {
one++;
}
}
}
return tot >= x;
}
signed main(){
cin >> N >> s;
int l = 0, r = N, mid;
while(l < r){
mid = (l + r + 1) >> 1;
if(check(mid)){
l = mid;
} else {
r = mid - 1;
}
}
cout << l << endl;
return 0;
}