CF549F(分治+启发式合并)

这道题的初始思路可以看的出来

是一道分治的思想,这种题往往枚举端点计算贡献

而这一题因为有个最大值的限制,所以我们考虑维护每个点作为最大值的答案

那么一般来说,都是在区间内,枚举首位,然后二分答案,但是这样复杂度会退化,例如一个很长的递增子序列

因此我们考虑启发式合并,前缀和后缀哪边小枚举哪边,这样复杂度就是log,因此这道题套了两个log

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=1e6+10;
const int mod=998244353;
ll sum[N],a[N];
vector<int> num[N];
int q[N];
int pre[N],suf[N];
int solve(int l,int r,int x){
    return upper_bound(num[x].begin(),num[x].end(),r)-lower_bound(num[x].begin(),num[x].end(),l);
}
int main(){
    ios::sync_with_stdio(false);
    int n,k;
    cin>>n>>k;
    int i;
    num[0].push_back(0);
    for(i=1;i<=n;i++){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
        num[sum[i]%k].push_back(i);
    }
    int hh=0,tt=0;
    q[0]=0;
    for(i=1;i<=n;i++){
        while(hh<=tt&&a[q[tt]]<=a[i]){
            tt--;
        }
        pre[i]=q[tt]+1;
        q[++tt]=i;
    }
    hh=0,tt=0;
    q[0]=n+1;
    a[n+1]=1e9+1;
    for(i=n;i>=1;i--){
        while(hh<=tt&&a[q[tt]]<a[i]){
            tt--;
        }
        suf[i]=q[tt]-1;
        q[++tt]=i;
    }
    ll ans=-n;
    for(i=1;i<=n;i++){
        if(suf[i]-i>i-pre[i]){
            for(int j=pre[i]-1;j<i;j++){
                ans=ans+solve(i,suf[i],(sum[j]+a[i])%k);
            }
        }
        else{
            for(int j=i;j<=suf[i];j++){
                int tmp=solve(pre[i]-1,i-1,(sum[j]-a[i])%k);
                ans+=tmp;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}
View Code

 

posted @ 2021-04-30 22:48  朝暮不思  阅读(95)  评论(0编辑  收藏  举报