原题链接: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; }