子串计数问题
Posted 桂月二四
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了子串计数问题相关的知识,希望对你有一定的参考价值。
定义
子串的定义:将一个字符串从开头与结尾删去任意字符,形成新的字符串,称为原字符串的子串。
子序列的定义:将一个字符串删去任意字符,形成新的字符串,称为原字符串的子序列。
区别就是:子串在原串中是连续的,子序列不一定。
子串计数问题,一般是给定一个字符串,求满足条件的子串的个数。
一般解题方法
遍历该字符串中的每个字符(for(i=1;i<=n;i++)),不重不漏的计算出以该字符为开头的方案数。答案就是每个字符答案的累加。
关键在于不重不漏的算出包含该字符的方案数
例题1:01序列
链接
解法1:考虑上面的一般解题方法,按照这种思路,我们只需要计算每一位为开头,满足要求的子串个数。对于每个字符,我们记从当前字符开始,恰好出现k个1的最前位置为l,恰好出现k个1的最后位置为r,那么答案就是r-l+1,实际上,l和r是可以通过二分的到的。我们对每个字符都这样进行计算,即可得到答案。注意特判k=0。
复杂度:O(nlogn)
代码
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
#define pb push_back
#define debug(x) cerr<<#x<<'='<<x<<'\\n'
#define debugg(x,y) cerr<<#x<<'='<<x<<','<<#y<<'='<<y<<'\\n'
#define FOR(a,b,c) for(int a=(b),a##_end=(c);a<=a##_end;++a)
#define ROF(a,b,c) for(int a=(b),a##_end=(c);a>=a##_end;--a)
#define FASTIO() cin.sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
#define sz(s) (int)(s.size())
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 1000010 ,M = 1000010,mod = 998244353,_=0;const double eps = 1e-9;
template<typename T>inline bool chkmin(T &x,const T &y)return y<x?x=y,1:0;
template<typename T>inline bool chkmax(T &x,const T &y)return x<y?x=y,1:0;
template <typename T> void inline read(T &x)
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') if (s == '-') f = -1; s = getchar();
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
int n,m,k;
int sum[N];
char s[N];
signed main()
read(k);
scanf("%s",s+1);
n = strlen(s+1);
if(k==0)
ll now = 0,ans = 0;
for(int i=1;i<=n;i++)
if(s[i]=='0')
++now;
else
ans = ans+(now+1)*now/2;
now = 0;
ans += (now+1)*now/2;
cout<<ans;
return 0;
for(int i=1;i<=n;i++)
sum[i] = sum[i-1]+(s[i]=='1');
ll ans = 0;
for(int i=1;i<=n;i++)
int l = i,r = n;
while(l<r)
int mid = l+r >>1 ;
if(sum[mid]-sum[i-1]>=k) r = mid;
else l = mid+1;
if(sum[l]-sum[i-1]!=k) break;
int pos = l;
l = i,r = n;
while(l<r)
int mid = l+r+1 >>1;
if(sum[mid]-sum[i-1]<=k) l = mid;
else r = mid-1;
ans+=l-pos+1;
cout<<ans;
return ~~(0 ^ _ ^ 0);
解法2: 其实本题不用上面的方法也可以做。对于两个中间‘1’个数恰好为k个的1,我们只需要计算一下这两个1前后0的个数,然后二者相乘即可。因此我们只需枚举两个中间‘1’个数恰好为k个的1,所得答案相加即可。
复杂度O(n)
代码:(写的有点复杂,特判有点多,写的好的话是可以写比较短的)
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
#define pb push_back
#define debug(x) cerr<<#x<<'='<<x<<'\\n'
#define debugg(x,y) cerr<<#x<<'='<<x<<','<<#y<<'='<<y<<'\\n'
#define FOR(a,b,c) for(int a=(b),a##_end=(c);a<=a##_end;++a)
#define ROF(a,b,c) for(int a=(b),a##_end=(c);a>=a##_end;--a)
#define FASTIO() cin.sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
#define sz(s) (int)(s.size())
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 1000010 ,M = 1000010,mod = 998244353;const double eps = 1e-9;
template<typename T>inline bool chkmin(T &x,const T &y)return y<x?x=y,1:0;
template<typename T>inline bool chkmax(T &x,const T &y)return x<y?x=y,1:0;
template <typename T> void inline read(T &x)
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') if (s == '-') f = -1; s = getchar();
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
int n,m,k;
int a[N];
char s[N];
int last[N],ne[N];
signed main()
read(k);
scanf("%s",s+1);
n = strlen(s+1);
ll ans = 0;
if(k==0)
ll now = 0;
for(int i=1;i<=n;i++)
if(s[i]=='0')
++now;
else
ans = ans+(now+1)*now/2;
now = 0;
ans += (now+1)*now/2;
cout<<ans;
return 0;
for(int i=n,j=n+1;i>=1;i--)
ne[i] = j;
if(s[i]=='1') j = i;
for(int i=1,j=0;i<=n;i++)
last[i] = j;
if(s[i]=='1') j = i;
int be = -1,ed = -1,now = 0;
for(int i=1;i<=n;i++)
if(s[i]=='1')
++now;
if(be==-1) be = i;
if(now==k)
ed = i;
break;
if(ed==-1)
puts("0");
return 0;
for(int i = be,j=ed;j<=n;)
ans = ans+(i-last[i])*(ne[j]-j);
i = ne[i],j = ne[j];
cout<<ans;
return 0;
我们主要讲第一种解法的应用,第二种解法的复杂度虽然低,但是写起来可能需要注意很多边界条件,出错的概率较大
题目2:R
链接
题解:继续使用上述一般解题方法的思路,我们依次考虑每个字符为开头的答案。发现本题比上一题还要简单,我们只要二分出从当前字母开始,字母R个数大于等于k的最前位置l,预处理出当前字母后面最接近的字母P的位置为r。答案即为max(0,r-l+1)
代码
#include <bits/stdc++.h>
#include<ext/rope>
#define pb push_back
#define debug(x) cerr<<#x<<'='<<x<<'\\n'
#define debugg(x,y) cerr<<#x<<'='<<x<<','<<#y<<'='<<y<<'\\n'
#define FOR(a,b,c) for(int a=(b),a##_end=(c);a<=a##_end;++a)
#define ROF(a,b,c) for(int a=(b),a##_end=(c);a>=a##_end;--a)
#define FASTIO() cin.sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define int long long
using namespace std;
using namespace __gnu_cxx;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 200010 ,M = 1000010,mod = 998244353;const double eps = 1e-9;
ll qmi(ll,ll);
template<typename T>inline bool chkmin(T &x,const T &y)return y<x?x=y,1:0;
template<typename T>inline bool chkmax(T &x,const T &y)return x<y?x=y,1:0;
template <typename T> void inline read(T &x)
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') if (s == '-') f = -1; s = getchar();
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
char s[N];
int p[N];
ll sum[N];
int ne[N];
int n,k;
signed main()
cin>>n>>k;
scanf("%s",s+1);
p[n+1] = n+1;
for(int i=n;i>=1;i--)
p[i] = p[i+1];
if(s[i]=='P')
p[i] = i;
ll ans = 0;
for(int i=1;i<=n;i++)
sum[i] = sum[i-1];
if(s[i]=='R') ++sum[i];
for(int i=1;i<=n;i++)
if(sum[n]-sum[i-1]<k)
ne[i] = 0;
break;
int l = i,r = n;
while(l<r)
int mid = l+r>>1;
if(sum[mid]-sum[i-1]>=k) r = mid;
else l = mid+1;
ne[i] = l;
for(int i=1;i<=n;i++)
if(ne[i]==0) break;
int maxd = p[i]-1;
ans+=max(0ll,maxd-ne[i]+1);
cout<<ans;
return 0;
ll qmi(ll a,ll b) ll res=1;a%=mod; for(;b;b>>=1)if(b&1)res=res*a%mod;a=a*a%mod;return res;
题目3:子串分值和
题解:前两题计算的是符合要求的子串的个数,这题要求每个子串的权值和。其实方法类似。我们继续使用上述一般解题方法的思路,计算每个字符为开头的答案。如果进行暴力,肯定会t。但是注意到,每个子串的答案都是1-26(最多26个字符),也就是说,固定第一个字符,右边界慢慢变大,答案必然递增,且最多上升26次。
不难想到,我们可以预处理出每个位置的下一个(‘a’-‘z’)出现的位置,看不懂这句话可以看这段代码
memset(a,-1,sizeof a);
for(int i=n;i>=1;i--)
a[s[i]-'a'] = i;
for(int j=0;j<26;j++)
ne[i][j] = a[j];
当我们计算时,对于每一个字符,假设其位置是i。我们只需要把ne[i] 中不是-1的值取出并且排序,只有当右边界的下标等于ne[i]中的值时答案才会上升。
复杂度O(26n)
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
#define pb push_back
#define debug(x) cerr<<#x<<'='<<x<<'\\n'
#define debugg(x,y) cerr<<#x<<'='<<x<<','<<#y<<'='<<y<<'\\n'
#define FOR(a,b,c) for(int a=(b),a##_end=(c);a<=a##_end;++a)
#回文子串解法大全