版权声明:转载标注来源喔~ https://blog.csdn.net/iroy33/article/details/89811697
题意:求有多少个子区间,区间乘积/区间和为k
思路:脑子里快速闪过一些念头,暴力的话是n^2,不OK。。尺取?不行,区间乘积/区间和不具备单调性
果断看题解。。。正确的思路是以数据量为切入点,乘积最大m为2e5*1e8*1e5=2e18
tip1:固定区间左端点去遍历更新右端点。由于1对乘积没有影响,跳过1,那么每次遍历的值都不小于2,由于数据总范围在long long以内,可知最多遍历64次就可结束。时间复杂度被缩减至O(64n)
tip2:那么如何考虑区间连续的1呢?它只对sum有影响,将我们现在的区间右端点位置记为last, 区间乘积为m,区间和为s,下一个大于1的数字的位置记为j,则last和j之间1的个数为j-last-1。如果m%k==0并且s+1<=m/k&&m/k<=s+(j-last-1),那么我们可以知道从last加到j的位置(不断加1),一定有个解
注意事项:思路比较明晰,但是容易些崩,我觉得我写不粗来。。。查找下一个非1的位置的时候,倒着遍历,注意处理最后一个数为1的情况
#include<iostream>
using namespace std;
const int N=2e5+10;
typedef long long LL;
int a[N];
int nxt[N];
int sum[N];
int n,k;
LL ans=0;
int main()
{
cin>>n>>k;
sum[0]=0;
for(int i=1;i<=n;++i)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
nxt[n+1]=-1; //最后一个数是1的情况需要用到
for(int i=n;i>=1;--i)
{
if(a[i]>1) nxt[i]=i;
else nxt[i]=nxt[i+1];
}
for(int i=1;i<=n;++i) //枚举区间左端点
{
LL s=a[i],m=a[i];
if(m==s*k) ++ans;
int j=i; //j记录上一个非1的位置
while(j<n)
{
int z=nxt[j+1]; //z记录下一个非1的位置
if(z==-1) z=n+1; //最后一个数是1,没有非1的位置
int ones=z-j-1; //两个非1数据之间连续的1
if(m%k==0&&m/k>=s+1&&m/k<=s+ones) //考虑s加上中间连续的1过程中是否有解
++ans;
if(z==n+1) break;
//接下来考虑下一个非1是否能乘上去
if(m<2e18/a[z]) //把区间右端点加上之后判断
{
m*=a[z];
s+=a[z]+ones;
j=z;
if(m==s*k)
{
++ans;
}
}
else break;
}
}
cout<<ans<<endl;
return 0;
}