题意:给定一个只包含0,1的字符串s,问在这个字符串当中,有多少个子串s[l-r]满足demical(s[l-r])=r-l+1,即子串长度等于它所表示的十进制数。
一开始没什么思绪,但是后来慢慢地发现,满足这个条件的子串似乎并不多。
我们要明确:前导0的贡献是使子串的长度增加1,而保持数字大小不变,而非前导0或者1的贡献是使长度增加1的同时数字增大1倍(若为1则再+1),因此我们从前往后读,前导0只会是长度增大而已,而当遇到1以后,数字大小会是指数型的增长的,这也导致了在长度足够的情况下它势必会超过子串长度。
于是,我们可以把思路捋一捋:
1.若当前位是0,则继续往下读,直到读到1,读到1以后,有以下3种情况:
[1].r-l+1>num,此时继续往下读;
[2].r-l+1=num,ans++;
[3].r-l+1<num,此时数字大小已经超过子串长度了,因为数字是指数型爆炸式增长的,因此子串长度大小后面无论怎么赶也赶不上了,因此这里就可以停止读数了。
2.若当前位是1,则ans++,再判断下一位是否为0,是的话就ans++。
代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
char s[maxn];
int pos[maxn]; //pos[i]是记录第i个1所在的位置,这样当我们读到0的时候,就可以二分找到下一个1的位置
int lower_bound(int p,int l,int r) //二分查找下届
{
int m;
while(l<r)
{
m=l+(r-l)/2;
if(p<=pos[m]) r=m;
else l=m+1;
}
return l;
}
int main()
{
int t;
scanf("%d",&t);
pos[0]=0;
while(t--)
{
scanf("%s",s+1);
int len=strlen(s+1);
int cnt=0;
for(int i=1;i<=len;++i)
if(s[i]-'0')
pos[cnt++]=i;
ll ans=0;
for(int i=1;i<=len;++i)
{
if(s[i]-'0') //是1
{
++ans;
if(i+1<=len&&s[i+1]=='0')
++ans;
}
else
{
int find=lower_bound(i,0,cnt);
if(pos[find]<=i) continue; //找到的1的位置比当前为还前,继续下次循环(也可以直接结束了)
ll temp=0;
for(int j=pos[find];j<=len;++j)
{
temp=(temp<<1)|(s[j]-'0');
if(temp==j-i+1)
{
++ans;
break;
}
else if(temp>j-i+1)
break;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
小结:有些题看着难,但其实并没有那么复杂的。