原题链接:P4062 [Code+#1]Yazid 的新生舞会

解法一

分块

对于所有,暴力扫描所有长度不大于的区间,即枚举左端点,然后往右扫描,长度不大于,然后维护众数出现的次数,当众数出现的次数大于区间的一半时,该区间对答案的贡献加一。这里注意不要把维护进众数里,后面会单独计算每个为众数的所有合法区间。

如果数组只由组成,计算以为众数的合法区间数量。表示前缀和。那么满足条件的区间需要满足:
表示右端点取,左端点合法的数量,考虑到后移,等于加一或者减一,那么右端点取后左区间和法的数量为

对于所有,这些不同的数字不会超过个,枚举每种为众数的贡献,用上面的方法。

在洛谷上交18s左右,改一下再交hdu应该会T,最慢的做法

原题的代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+7,maxm=1e6+7,m=5e5+1;
int a[maxn],f2[maxn],cnt[maxm];
bool vis[maxn];

unordered_set<int>s;
signed main() {
    int n,t;
    scanf("%d%*d",&n);
    t=sqrt(n);
    for(int i=1; i<=n; ++i) {
        scanf("%d",&a[i]);
        s.insert(a[i]);
        ++cnt[a[i]];
    }
    ll ans(0);
    int c2(0);
    for(int num:s) {
        if(cnt[num]<t) vis[num]=1;
        else f2[++c2]=num;
        cnt[num]=0;
    }
    for(int i=1,r,maxx; i<=n; ++i) {
        r=i+2*t-1;
        if(r>n) r=n;
        maxx=0;
        for(int l=i; l<=r; ++l) {
            if(vis[a[l]]) ++cnt[a[l]];
            maxx=maxx<cnt[a[l]]?cnt[a[l]]:maxx;
            if(maxx>(l-i+1)/2) ++ans;
        }
        for(int l=i; l<=r; ++l) {
            if(vis[a[l]]) --cnt[a[l]];
        }
    }
    for(int i=1,sum,res; i<=c2; ++i) {
        res=sum=0;
        for(int k=0;k<maxm;k++) cnt[k]=0;
        ++cnt[m];
        for(int j=1; j<=n; ++j) {
            if(a[j]==f2[i]) res+=cnt[2*sum-(j-1)+m],++sum;
            else res-=cnt[2*sum-(j-1)-1+m];
            ans+=res;
            ++cnt[2*sum-j+m];
        }
    }
    printf("%lld\n",ans);
    return 0;
}

解法二、三

思路

求数为众数的区间数时,原数组中等于的数看作,不等于的数看作,答案是把每个位置前缀和后去数前面有几个前缀和比当前缀和小的。

求数为众数的区间数,原数组,化为数组,每个位置的贡献为

图片说明

树状数组

这个跑得最快了

表示处理后的数组,前缀和,假设当前位置的前缀和为,那么当前位置的贡献为

求数为众数的区间数时,如果有,那么可以分成个区间(以每个开头或开头,到下个之前的数),当时,上面的原数组可以分为。可以发现同一段是不会有任何贡献的,因为同一段的前缀和是递减的。

假设某一段为,那么我们可以每次询问计算这一段区间对答案的总贡献,然后然后对

找到怎么用数据结构维护,假设表示的差分数组。

于是我们只需要用树状数组维护。用线段树的话,如果直接维护前缀和,可能常数会大些,需要的空间会更大。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+7,maxm=2e6+7,m=1e6+1;
ll t1[maxm],t2[maxm],t3[maxm];
inline void add(int x,ll d) {
    ll k=x;
    while(x<maxm) {
        t1[x]+=d;
        t2[x]+=d*k;
        t3[x]+=d*k*k;
        x+=x&-x;
    }
}
inline ll sum(int x) {
    ll res(0),k=x;
    while(x) {
        res+=t1[x]*(k+2)*(k+1)-t2[x]*(2*k+3)+t3[x];
        x-=x&-x;
    }
    return res/2;
}
int a[maxn];
vector<int>v[maxn];
unordered_set<int>s;
signed main() {
    int T,n;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        s.clear();
        for(int i=1; i<=n; ++i) {
            scanf("%d",&a[i]);
            s.insert(a[i]);
            v[a[i]].emplace_back(i);
        }
        ll ans(0);
        int x,y,pre,cnt;
        for(int num:s) {
            v[num].emplace_back(n+1);
            pre=cnt=0;
            for(int i:v[num]) {
                y=2*cnt-pre+m,x=2*cnt-(i-1)+m;
                ans+=sum(y-1)-sum(x-2);
                add(x,1);
                add(y+1,-1);
                ++cnt;
                pre=i;
            }
            pre=cnt=0;
            for(int i:v[num]) {
                y=2*cnt-pre+m,x=2*cnt-(i-1)+m;
                add(x,-1);
                add(y+1,1);
                ++cnt;
                pre=i;
            }
            v[num].clear();
        }
        printf("%lld\n",ans);
    }
    return 0;
}

考虑到前缀和的连续性,取为众数,原数组中等于的数看作,不等于的数看作

表示处理后的数组前项的前缀和,表示第项的贡献,表示第项的贡献,表示处理后的数组前项的前缀和。

时,明显有;反之,有

如果这样做复杂度是的。假设前面出现的最小的前缀和为,当时,可以直接跳到下一个的位置,因为被跳过的这一段对答案没有贡献,然后用差分数组标记,在起点加一,在终点减一。

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+7;
int a[maxn];
vector<int>v[maxn];
unordered_set<int>s;
unordered_map<int,int>f1,f2;
signed main() {
    int T,n;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        s.clear();
        for(int i=1;i<=n;++i) {
            scanf("%d",&a[i]); s.insert(a[i]); v[a[i]].emplace_back(i);
        }
        ll ans(0),res;
        int sum,minn,k;
        for(int num:s) {
            v[num].emplace_back(n+1);
            f1.clear(),f2.clear();
            res=k=minn=sum=0;
            for(int i=1;i<=n;++i) {
                if(a[i]!=num&&sum==minn) {
                    int len=v[num][k]-i-1;
                    --f2[sum+1];
                    ++f2[sum-len];
                    i+=len;
                    sum-=len+1;
                }
                else if(a[i]==num) {
                    f1[sum]+=f2[sum];
                    f2[sum+1]+=f2[sum];
                    f2[sum]=0;
                    ++f1[sum];
                    res+=f1[sum];
                    ++sum;
                    ans+=res;
                    ++k;
                }
                else {
                    ++f1[sum];
                    --sum;
                    res-=f1[sum];
                    ans+=res;
                }
                if(minn>sum) minn=sum;
            }
            v[num].clear();
        }
        printf("%lld\n",ans);
    }
    return 0;
}