嘘~ 正在从服务器偷取页面 . . .

模拟赛题讲解[13]


模拟赛题讲解[13]

来自 yukuai26 2022-08-06 noi.ac #2757

据说是从JOI搬过来的,赛时有位巨佬18min就A了 Orz

题目描述

橙 —— 作为幽幽子大人的朋友的使者, 非常贪玩, 也喜欢思考有趣 的问题。

她在冥界思考一个有趣的问题, 希望赶到的灵梦能够解答。

一个字符串由 $\tt{a,b,c}$三种字母组成, 它最多能分成多少个 $\tt{abc}$ 和 $\tt{cbc}$ 的子序列呢?

具体的, 一个字符串 $str$ 可以由选择 $3$ 个位置 $i < j < k$ 使得 $s_i,s_j,s_k$ 依次连接组成 $\tt{abc}$ 或 $\tt{cbc}$ ,

这样算分出一个字符串, 接着把 $3$ 个位置上的字母删掉, 剩下的字母重新组成新的字符串并接着提取。

输入格式

第一行一个正整数 $n$ 表示字符串的长度

接下来一个长度为 $n$ 的字符串, 保证由 $\tt{a,b,c}$ 组成

输出格式

一行一个数,表示最大能分成的个数

输入1

6
abcbcc

输出1

2

输入2

5
abcbb

输出2

1

数据范围

打包测试

对于测试包 $1$,分值 $40$ 分,$n \leq 15$

对于测试包 $2$,分值 $30$ 分,$n \leq 50$

对于测试包 $3$,分值 $20$ 分,$n \leq 3000$

对于测试包 $4$,分值 $10$ 分,$n \leq 10^6$

题解

Subtask 1 40pts

注意到 $\tt{c}$ 可以作为结尾,也可以作为开头

考虑对于所有的 $\tt{c}$ 暴搜他作为第一个还是第三个

如果是第一个就变成 $\tt{a}$ ,计算即可。

Subtask 2 30pts

考虑从后往前做

设 $f_{i,j,k}$ 表示最后 $i$ 位,有 $j$ 个 $\tt{bc}$ ,$k$ 个 $\tt{c}$ ,最多能有多少 $\tt{abc}$ 或 $\tt{cbc}$ ,转移 $O(1)$。

Subtask 3 20pts

把所有作为 $\tt{abc}$ 和 $\tt{cbc}$ 末尾的 $\tt{c}$ 放在原串的最后面

具体地,如果串里面出现了

我们强制做如下匹配

这样的好处在于,对于合法的方案,一定存在一个分界线,满足

  • 分界线左侧的所有 $\tt{c}$ 均可以看作 $\tt{a}$
  • 分界线右侧仅由 $\tt{bc}$ 构成,并且与分界线左侧每个 $\tt{a/c}$ 两两配对

考虑枚举这个分界线,时间复杂度 $O(n^2)$

Subtask 4 10pts

注意到如果右侧的 $\tt{bc}$ 不够了,分界线理应往左移

如果左侧的 $\tt{c}$ 过多了,分界线理应往右移

实际上是存在单调性的。考虑二分分界线。

时间复杂度 $O(n \log n)$ ,可以通过此题

代码:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1e6+15)

int n;
bool vis[N];
char s[N];
bool check(int mid)
{
    for(int i=1; i<=n; i++) vis[i]=0;
    int cnt_a=0,cnt_b=0,cnt_c=0;
    for(int i=n; i>=1; i--)
    {
        if(s[i]=='c'&&cnt_c<mid) vis[i]=1,++cnt_c;
        else if(s[i]=='b'&&cnt_b<cnt_c) vis[i]=1,++cnt_b;
    }
    if(cnt_b < mid)return 0;
    cnt_a=cnt_b=0;
    for(int i=n; i>=1; i--)
    {
        if(s[i]=='b'){if(vis[i]) ++cnt_b; else continue;}
        else if(!vis[i] && cnt_a < cnt_b) ++cnt_a;
    }
    return cnt_a==mid;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> (s+1);
    int l=0,r=n/3,mid;
    while(l<r)
    {
        mid=(l+r+1) >> 1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    cout << l << '\n';
    return 0;
}

Extra 更快的解法

时间复杂度 $O(n)$ ,目前q779还不会,因此把老师的题解搬过来了

O(n)
考虑从后往前做,记录bc和c的个数
对于已经有bc时出现c的情况,先匹配成cbc,并记录这个c的位置
对于没有c并且出现b的情况,也记录这个b的位置
这时出现a并且没有bc,我们考虑反悔操作,把前面的cbc拆开变成abc,这时候会多出一个c,根据前面记录的b的位置与c的位置视情况会多出一个c或者bc

老师的代码:

#include<bits/stdc++.h>
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);i++)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);i--)
#define ll long long
using namespace std;
const int N=1000005;
int n,ans;
char s[N];
int f[N][3];
int g[N][3];
int main(){
	scanf("%d%s",&n,s+1);
	For(i,1,n){
		For(j,0,2) f[i][j]=f[i-1][j];
		if (s[i]=='a'||s[i]=='c') ++f[i][0];
		else if (f[i][0]) --f[i][0],++f[i][1];
	}
	Rep(i,n,1){
		For(j,0,2) g[i][j]=g[i+1][j];
		if (s[i]=='c') ++g[i][0];
		else if (s[i]=='b'){
			if (g[i][0]) --g[i][0],++g[i][1];
		}
		else{
			if (g[i][1]) --g[i][1],++g[i][2];
		}
	}
	For(i,0,n){
		int sum=g[i+1][2],v;
		v=min(f[i][0],g[i+1][1]);
		sum+=v; f[i][0]-=v; g[i+1][1]-=v;
		v=min(f[i][1],g[i+1][0]);
		sum+=v; f[i][1]-=v; g[i+1][0]-=v;
		v=min(f[i][1],g[i+1][1]);
		sum+=v; f[i][1]-=v; g[i+1][1]-=v;
		ans=max(ans,sum);
	}
	printf("%d\n",ans);
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录