CF1400E Clear the Multiset 以及一道不知名的题

CF1400E Clear the Multiset

一道经典简单的分治

由贪心可知,对于一段区间[L,R],一共有两种处理方式

1.一个一个减,次数为l-r+1

2.先区间减,直到最小的减没了,在考虑最小值隔开的两个区间。如果有多个最小值,其实也不影响,再往下分的时候一定会分开。区间答案就是 $min(l-r+1,f(l,p-1)+f(p+1,r)+minn)$ 具体含义见代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int a[N],n;
int work(int l,int r){
    if(l>r) return 0;
    if(l==r) return min(a[l],1);
    int minn=2e9+555,p;
    for(int i=l;i<=r;i++){
        if(minn>a[i]){
            minn=a[i];p=i;
        }
    }
    for(int i=l;i<=r;i++) a[i]-=minn;
    return min(r-l+1,work(l,p-1)+work(p+1,r)+minn);
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    cout<<work(1,n);
    return 0;
}

 Distribution

Cakzy have a sequence a[1...n], and a[i] represents the number of candies in i-th candy pile. And every candy is the same. There are k children around Cakzy. Cakzy want to select an interval [l, r] (1 ≤ l ≤ r ≤ n) in the sequence. He will take away the maxmium pile in the interval [l, r]. Then he will mix the remaining candies of the interval [l, r] into one pile and distribute them equally to the k children. Attention:: One candy can not be divided into several pieces. Cazky wants to know how many intervals can meet his demand. Input The first line contains two integers n (1 ≤ n ≤ 3 × 105 ), m (1 ≤ m ≤ 3 × 106 ). Following one line contains n integers a[i] (1 ≤ a[i] ≤ 10000), denoting the number of candies in i-th pile. Output Output one line containing one decimal, denoting the answer. Example standard input standard output 4 3 1 2 3 4 3 Note In the example test, n = 4, m = 3. The 3 intervals are [1, 3], [1, 4], [3, 4].

一言以蔽之,就是对于一个区间,他合法当且仅当去掉最大值,他的和可以被区间长度整除。问总区间里有多少个子区间符合题意(必须连续)。

考虑分治。对于一个区间,我们只处理横跨中点的情况,左右子区间里的递归下去,这样就可以不重不漏处理所有情况。对于左区间,我们钦定一个最大值,向左扫,并记录后缀和,后缀最大值,然后享有,枚举,直到碰见更大的值,开一个桶,统计各个余数的个数,然后统计答案。同样的方法处理最大值在右区间的情况。注意处理右边之前,左边的情况数要减回来。

// ubsan: undefined
// accoders
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e6+66;
int a[N],ans,cnt[N];
int n,m;
void work(int l,int r){
    if(l==r) return;
    int M=(l+r)>>1;
    work(l,M);work(M+1,r);
    int u=M,mxl=0,suml=0,mxr=0,sumr=0;//只处理横跨左右区间的情况,
                                      //其他情况直接递归下去
    //左:                                  
    for(int i=M;i>=l;i--){//相当于已经钦定了一个最大值
        mxl=max(mxl,a[i]);
        suml+=a[i];//记录后缀和,后缀最大值
        while(u<r&&a[u+1]<mxl){//向右枚举时不能大于钦定的最大值
            u++;
            sumr=(sumr+a[u])%m;
            cnt[sumr]++;//余数为 sumr 的情况数
        }
        ans+=cnt[(m-((suml-mxl)%m))%m];
    }
    sumr=0;
    for(int i=M+1;i<=u;i++){
        sumr=(sumr+a[i])%m;
        cnt[sumr]--;
        //处理右边之前,左边的情况数要减回来
    }
    //右:   与左基本相同
    u=M+1,mxl=0,suml=0,mxr=0,sumr=0;
    for(int i=u;i<=r;i++){
        mxr=max(mxr,a[i]);sumr+=a[i];
        while(u>l&&a[u-1]<=mxr){
            u--;
            suml=(suml+a[u])%m;
            cnt[suml]++;
        }
        ans+=cnt[(m-((sumr-mxr)%m))%m];
    }
    suml=0;
    for(int i=M;i>=u;i--){
        suml=(suml+a[i])%m;
        cnt[suml]--;
    }
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    work(1,n);
    cout<<ans;
    return 0;
}

 

posted @ 2023-06-24 19:07  DPD  阅读(29)  评论(0)    收藏  举报