AtCoder Beginner Contest 381
这场比赛打的冷汗直流,然后无奈寄掉。
C - 11/22 Substring
本以为直接暴力就可以,但是需要加前缀和优化,一个正向处理,一个反向处理,然后查找/
。
abc381_d
赛前2分钟hack掉自己的代码,然后寄掉。
双指针
答案必须是连续的区间,所以想到双指针维护区间合法性,但需要处理以下细节:
-
\(a_r\neq a_{r+1}\) 单个数不合法,右移右指针直到成立,此时与之前区间有断开,右移左指针到右指针。
-
\(vis_{a_r} \neq 0\) 说明当前的数区间包含过,右移左指针直到 \(vis_{a_r}= 0\),移除遍历过的数的标记。
-
\(a_r=a_{r+1} \& a_r=a_{r+2}\) 区间必须断开,如果 \(vis_{a_r}=0\) 要先计算答案,右移右指针,右移左指针到右指针。
#include <bits/stdc++.h>
#define int long long
#define re register
const int N=2e5+10;
const int mod=998244353;
using namespace std;
int n;
int a[N];
int vis[N];
int ans=0;
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
a[n+1]=-1;
int l=1,r=1;
while(l<=n&&r<=n){
int flag=0;
if(a[r]==a[r+1]&&a[r]==a[r+2]){
if(!vis[a[r+2]]){
ans=max(ans,r+2-l);
}
flag=1;
while(a[r]==a[r+1]&&a[r]==a[r+2]){
r++;
}
}
while(a[r]!=a[r+1]){
flag=1;
r++;
}
if(r>n){
break;
}
if(flag){//有断开标记
while(l<r){
vis[a[l]]=0;
l++;
}
}
if(vis[a[r]]){
while(vis[a[r]]){
vis[a[l]]=0;
l+=2;
}
}
vis[a[r]]=1;
if(a[r]!=a[r+2]){
r+=2;
}
ans=max(ans,r-l);
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
[ABC381E] 11/22 Subsequence
赛后的数据才是这个题的精髓,他和C题一样,只是加了个区间,那么我们就找区间,先把/
下标存起来,我就是没存吃大亏,然后二分查找在区间内的下标,然后就遍历计算,这样在赛时是可以过的,赛后就不可以了,我们还可以再二分查找,如果左部分数大于右部分那就缩小右端点,其中不断更新答案最大值。
[ABC381F] 1122 Subsequence
看到数字个数非常少,考虑状压。
我们想要分成并不相交的多个部分,设状态 \(f_s\) 为 \(s\) 状态选出的数中到达最右边的位置,每次从当前状态没有的数字转移,找到 \(f_s\) 右边第一个当前数字的位置 \(now\),再从 \(now\) 右边找到第一个这个数字,这个可以预处理出来。
最后遍历状态如果 \(f_s\le n\) 就说明状态合法,统计选的数字个数。