[CF549F/51nod1472]Yura and Developers

本文介绍了一种寻找特定性质子段的高效算法。该算法通过枚举子段的最大值并利用单调栈确定区间边界,结合预处理求和及哈希表优化统计过程,解决了在大规模数组中快速查找符合条件子段的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

有一个长度为n的数组a,现在要找一个长度至少为2的子段,求出这一子段的和,然后减去最大值,然后对k取余结果为0。
问这样的子段有多少个。

数据范围

1 ≤ n ≤ 300 000, 1 ≤ k ≤ 1 000 000, 1 ≤ ai ≤ 109

分析

首先有一个思路:枚举每个最大值,然后确定出以它为最大值的区间,能向左(或右)扩展到哪里,这个可以打两次单调栈解决。
剩下就是统计答案了。
首先设si=ij=1a[j]假设最大值为am,区间能向左(右)扩展到l,r。如果一个以am为最大值的区间[i,j](l≤i≤m,m≤j≤r)能够满足条件,当且仅当满足:(s[j]-s[i-1]-a[m])%k=0

统计答案

考虑枚举上述区间的左(或右)边界,然后就是插入一个形如(l,r,x)的询问,询问有多少个si (i∈[l,r]且si=x)。假设有q个询问,解决的时间复杂度为O(q+n)
如果对于一个m,以am为最大值可以扩展到l,r,那么相当于把区间[l,r]切成两部分,然后如果左半部分比右半部分小,就枚举左边界,否则枚举右边界,那么q是nlogn的。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

#define fi first
#define se second

using namespace std;

const int maxn=300005,maxm=1000005;

typedef long long LL;

typedef pair<int,int> PII;

int n,m,a[maxn],s[maxn],l[maxn],r[maxn],lt[maxn],rt[maxn],top,st[maxn],rank[maxn],data[maxn],tot,cnt[maxm];

vector <int> pl[maxn],mi[maxn];

LL ans;

PII A[maxn];

bool cmp(PII a,PII b)
{
    return a.fi<b.fi;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        s[i]=(s[i-1]+a[i])%m;
        A[i].fi=a[i]; A[i].se=i; a[i]%=m;
    }
    sort(A+1,A+n+1,cmp);
    for (int i=1;i<=n;i++) rank[A[i].se]=i;
    st[top=1]=1;
    for (int i=2;i<=n;i++)
    {
        if (rank[st[top]]<rank[i])
        {
            for (;top && rank[st[top]]<rank[i];top--);
            lt[i]=st[top+1];
        }
        st[++top]=i;
    }
    st[top=1]=n;
    for (int i=n-1;i;i--)
    {
        if (rank[st[top]]<rank[i])
        {
            for (;top && rank[st[top]]<rank[i];top--);
            rt[i]=st[top+1];
        }
        st[++top]=i;
    }
    data[tot=1]=A[n].se;
    r[data[1]]=n+1;
    for (int i=1;i<=tot;i++)
    {
        int x=data[i];
        if (x-l[x]<=r[x]-x)
        {
            for (int j=l[x];j<x;j++)
            {
                mi[x-1].push_back((s[j]+a[x])%m);
                pl[r[x]-1].push_back((s[j]+a[x])%m);
            }
        }else
        {
            for (int j=x;j<r[x];j++)
            {
                if (l[x]) mi[l[x]-1].push_back((s[j]+m-a[x])%m);
                pl[x-1].push_back((s[j]+m-a[x])%m);
            }
        }
        if (lt[x])
        {
            l[lt[x]]=l[x]; r[lt[x]]=x; data[++tot]=lt[x];
        }
        if (rt[x])
        {
            l[rt[x]]=x; r[rt[x]]=r[x]; data[++tot]=rt[x];
        }
    }
    ans=0;
    for (int i=0;i<=n;i++)
    {
        cnt[s[i]]++;
        vector <int> ::iterator it;
        for (it=mi[i].begin();it!=mi[i].end();it++) ans-=cnt[*it];
        for (it=pl[i].begin();it!=pl[i].end();it++) ans+=cnt[*it];
    }
    printf("%I64d\n",ans-n);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值