本来以为是三月末甚至四月初才面试,所以当时学弟找我打校赛我就答应了(我说小概率时间可能会冲突,他说没事还有另一个大爹然后发现是我队友,没绷住 )。结果校赛时间定在21号,我20号晚上收到的面试通知,要我24号就面试,没招了。不过不是硬性冲突,就当是给机试练练手,所以我就还是上了。
这场赛时做起来不太顺,可能是太长时间没打所以手比较生。反正罚时吃的比较多,最后憋了两道题也没憋出来,才弄了个校第二,没招了。其实好像也不冤,毕竟我们队俩主力,还一年没认真训练了,而大三学弟队是经过大二一整年心无旁骛的训练的,所以水平没准比我们还强不少。不过就算同时期,也肯定是比不过我们的学长他们了,我刚入学那会真是看神仙打架,现在学长们都走了,哎感慨万千。
今天刚回,趁着还有点记忆说说 L 题,这题真不难,放大二的时候我们队估计直接秒了,气的我队友(还有一个在出题组,当天在当志愿者)指着我俩鼻子骂:"你们忘本了知不知道?"(他说这是当时我们进实验室那场选拔赛的题,但是我真没啥记忆了)。
Problem L. ZZUPC
- Input file:
standard input - Output file:
standard output - Time limit: 2 second
- Memory limit: 256 mebibytes
Zerc 在「浪潮杯」郑州大学第 17 届大学生程序设计竞赛 / The 17th ZZUPC 开始前向服务器祈祷。
突然,屏幕上出现了一个长度为 nnn 的仅由大写英文字母 Z U C P 组成的字符串 SSS。
下面还有一行文字:If you can find the maximum length of the lucky string, then I can bless this competition to go smoothly.
定义字符串 TTT 为幸运字符串 ,当且仅当:TTT 的字符可以通过重新排列,变成 (ZZUPC)×k(ZZUPC) \times k(ZZUPC)×k(即 kkk 个 ZZUPC 字符串首尾拼接而成,kkk 为非负整数)。
例如:
ZZUPC是幸运字符串(k=1k=1k=1);ZZUPCPCZZUPC是幸运字符串(k=2k=2k=2);ZUPCPCZ可以重新排列为ZZUPC,因此也是幸运字符串(k=1k=1k=1);ZZUCP无法重新排列为ZZUPC × k,因此不是幸运字符串。
现在给定字符串 SSS,请你求出 SSS 的所有子串中,最长的幸运字符串的长度。
子串 (Substring) :若字符串 ttt 可以表示为 sss 中一段连续的字符序列,则称 ttt 是 sss 的子串。
形式化地,若存在 1≤l≤r≤∣s∣1 \le l \le r \le |s|1≤l≤r≤∣s∣,使得 t=slsl+1...srt = s_l s_{l+1} \dots s_rt=slsl+1...sr,则 ttt 是 sss 的子串。
例如:s=abcdes = abcdes=abcde,则 bcd、a、de 均为其子串。而 ae 不是其子串。
Input
第一行输入一个整数 TTT (1≤T≤51 \le T \le 51≤T≤5),表示测试用例的数量。
对于每组测试样例:
- 第一行一个整数 nnn (1≤n≤2×1051 \le n \le 2 \times 10^51≤n≤2×105),代表字符串长度;
- 第二行一个长度为 nnn 的字符串 SSS,且仅包含字符 Z、U、C、P。
Output
对于每个测试用例,输出一个整数,表示最长的幸运字符串的长度。
Examples
输入1
cpp
3
6
ZZUPC
8
ZZZUUCCP
13
ZZZZZUCPCUCPC
输出1
cpp
6
0
12
Note
- 第一个测试用例:字符串本身就是
ZZUPC,长度为 6,对应 k=1k=1k=1。 - 第二个测试用例:不包含幸运字符串。
- 第三个测试用例:第 2 到 13 字符可以通过重组得到
ZZUPCPCZZUPC,故幸运字符串的最大长度为 12。
思路:
一开始的思路是:枚举最长的幸运字符串长度,对每种字符枚举右端点,找左端点的位置区间,然后四种字符的合法区间取交集。但是这个幸运字符串长度是不满足单调性的(即存在长的幸运字符串,不一定存在短的幸运字符串),因此无法二分答案。这样无论如何都无法做到低于 O(n2)O(n^2)O(n2) 的时间复杂度,何况还要找四种字符的左端点位置区间的交集。
正解是这样的:对一个长为 6k6k6k 的幸运字符串,它有 2k2k2k 个 Z, kkk 个 U, kkk 个 P, 2k2k2k 个 C。不妨先看 U 和 P,如果我们边处理边存储 前缀中 P 减去 U 的个数,那么我们就可以在 O(n)O(n)O(n) 的时间,找到对所有右端点,满足前缀中 P 减去 U 的个数相同的所有左端点。
仔细想想其实也挺显然的,本质是把两种字符的相对数量用前缀和进行维护 。证明也很显然,不妨设 uiu_{i}ui 表示字符串 1∼i1\sim i1∼i 前缀中 uuu 的个数,同理 pip_ipi。那么有:ur−ul−1=pr−pl−1u_r-u_{l-1}=p_r-p_{l-1}ur−ul−1=pr−pl−1ur−pr=ul−1−pl−1u_r-p_r=u_{l-1}-p_{l-1}ur−pr=ul−1−pl−1 这样做的好处是,查区间和会被转化为对一个前缀和,查找另一个前缀和。如果我们边处理前缀和,并以当前位置的前缀和为区间右端点,找前面已经处理出来的左端点(前面的前缀和可以用哈希表或者查找树进行处理),一遍遍历就可以找到所有情况。
所以做了 P-U 的前缀和,同理我们可以维护 Z-2U 和 C-2U 的前缀和,做一遍前缀和这个题就做完了。
所以我们现在只需要再做一个三元组的哈希,然后把哈希扔进哈希表里就行了。
代码:
没有OJ所以不能保证代码正确
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unordered_map>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;
int n;
string str;
unordered_map<ll,int> mp;
ll s2i(array<int,3> state){
ll pw=2*maxn;
return (state[0]+maxn)*pw*pw+
(state[1]+maxn)*pw+
(state[2]+maxn);
}
void solve(){
cin>>n>>str;
array<int, 3> state{0,0,0};//Z-2U C-2U P-U
mp.clear();
mp.insert(make_pair(s2i(state),-1));
int ans=0;
for(int i=0;i<str.length();i++){
char c=str[i];
if(c=='U'){
state[0]-=2;
state[1]-=2;
state[2]-=1;
}
else if(c=='Z')state[0]+=1;
else if(c=='C')state[1]+=1;
else if(c=='P')state[2]+=1;
ll val=s2i(state);
if(mp.find(val)!=mp.end())ans=max(i-mp[val],ans);
else mp.insert(make_pair(val,i));
}
cout<<ans<<endl;
}
int main(){
cin.tie(0)->sync_with_stdio(false);
int T=1;
cin>>T;
while(T--)solve();
return 0;
}
/*
3
6
ZZUCPC
8
ZZZUUCCP
13
ZZZZZUCPCUCPC
*/