【思维】codeforces 992D. Nastya and a Game

版权声明:转载标注来源喔~ 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;

}

猜你喜欢

转载自blog.csdn.net/iroy33/article/details/89811697