智乃去注册账号,他发现网站的的密码必须符合以下几个条件
- 密码是仅包含大小写英文字母、数字、特殊符号的字符串。
- 密码的长度不少于L{L}L个字符,并且不多于R{R}R个字符。
- 密码中应该至少包括①大写英文字母、②小写英文字母、③数字、④特殊符号这四类字符中的三种。
所谓特殊字符,是指非大小写字母、数字以及空格回车等不可见字符的可见字符,包括但不限于"~!@#$%^&*()_+"。
现在智乃有一个长度大小为N{N}N的字符串S{S}S,她想知道S{S}S串中有多少个子串是一个符合条件的密码,请你帮助智乃统计符合条件的密码数目。
子串是指字符串中某一段连续的区间,例如对于字符串"abcde"来说,"abc","cde"都是它的子串,而"ace"不是它的子串。
输入描述:
第一行输入三个正整数N,L,R(1≤N≤105,1≤L≤R≤N){N,L,R(1\le N \le 10^5,1\le L\le R \le N)}N,L,R(1≤N≤105,1≤L≤R≤N),表示S{S}S串的长度,合法密码长度应该在L{L}L到R{R}R个字符之间。
接下来一行输入一个长度为N{N}N的字符串S{S}S,字符串仅包括①大写英文字母、②小写英文字母、③数字、④特殊符号四类字符。
输出描述:
仅一行一个整数,表示有多少子串是一个合法的密码。
示例1
输入
10 6 8 asdfeg111*
输出
3
说明
"eg111*","feg111*","dfeg111*"
思路:假设x是最小满足的个数,如果y>=x,那么y是不是也是满足答案的,这不就是转化为了最大中找最小,那么就要开始二分了。我们直接二分区间,因为是区间,我们记录的满足条件也要是区间,所以这里我们就要用前缀和记录每个种类的个数了。
二分区间: 1.我们求的是个数,不是值,所以要二分区间。
2.二分区间只需要将当前位置i与mid传进去即可。
数组从1开始的:1.如果想要从L开始,就要减去i带来的偏移量。(注意从哪个位置开始,是否是 (求位置) 当前的L位置开始,如果是L当前位置开始就需要减去偏移量)
2.区间的循环要减去一段,如果下文的位置需要减去偏移量,那么这里就需要 再减去的部分加上偏移量。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
const int N=1e5+10;
int a[N],st[N],b[N],c[N],d[N];
int n,L,R;
char s[N];
bool chk(int l,int r)
{
int x=a[r]-a[l-1],y=b[r]-b[l-1],z=c[r]-c[l-1],p=d[r]-d[l-1];
if((x>=1&&y>=1&&z>=1)||(x>=1&&y>=1&&p>=1)||(x>=1&&z>=1&&p>=1)||(y>=1&&z>=1&&p>=1))return 1;
return 0;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin>>n>>L>>R;
cin>>s+1;
for(int i=1;i<=n;i++)
{
a[i]=a[i-1];
b[i]=b[i-1];
c[i]=c[i-1];
d[i]=d[i-1];
if(s[i]>='A'&&s[i]<='Z') a[i]++;
else if(s[i]>='a'&&s[i]<='z') b[i]++;
else if(s[i]>='0'&&s[i]<='9') c[i]++;
else d[i]++;
}
int ans=0;
for(int i=1;i<=n-L+1;i++)//i会减一,将最后允许的位置补上
{
int l=min(n,i+L-1),r=min(n,i+R-1);//减1是为了消除i带来的偏移量,可以让从l位置开始
int y=r;
while(l<r)
{
int mid=l+r>>1;
if(chk(i,mid))r=mid;
else l=mid+1;
}
if(!chk(i,r))continue;
ans+=y-r+1;
}
cout<<ans<<endl;
return 0;
}