原题链接:P4062 [Code+#1]Yazid 的新生舞会
解法一
分块 %7D&preview=true)
对于所有的
,暴力扫描所有长度不大于
的区间,即枚举左端点,然后往右扫描,长度不大于
,然后维护众数出现的次数,当众数出现的次数大于区间的一半时,该区间对答案的贡献加一。这里注意不要把
的
维护进众数里,后面会单独计算每个
的
为众数的所有合法区间。
如果数组只由组成,计算以
为众数的合法区间数量。
表示前缀和。那么满足条件的区间需要满足:
。
记表示右端点取
,左端点合法的数量,考虑到
后移,
等于
加一或者减一,那么右端点取
后左区间和法的数量为
或
对于所有的
,这些不同的数字不会超过
个,枚举每种
为众数的贡献,用上面的方法。
在洛谷上交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;
} 解法二、三
思路
求数为众数的区间数时,原数组中等于
的数看作
,不等于
的数看作
,答案是把每个位置前缀和后去数前面有几个前缀和比当前缀和小的。
求数为众数的区间数,原数组
,化为数组
,每个位置的贡献为
。
树状数组 %7D&preview=true)
这个跑得最快了
设表示处理后的数组,前缀和
,假设当前位置的前缀和为
,那么当前位置的贡献为
。
求数为众数的区间数时,如果有
个
,那么可以分成
个区间(以每个
开头或
开头,到下个
之前的数),当
时,上面的原数组可以分为
。可以发现同一段是不会有任何贡献的,因为同一段的前缀和是递减的。
假设某一段为,那么我们可以每次询问
计算这一段区间对答案的总贡献,然后然后对
。
找到怎么用数据结构维护,假设
表示
的差分数组。
于是我们只需要用树状数组维护。用线段树的话,如果直接维护前缀和
,可能常数会大些,需要的空间会更大。
#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;
} 
京公网安备 11010502036488号