【Solution】P8313 [COCI2021-2022#4] Izbori

前言

洛谷博客链接
很棒的一道题,建议评紫。

这里提供一种 O(nlogn) 的树状数组的解法,并有详细的部分分题解,可能比较长,如果只想看正解可以直接看 Subtask 4。

Subtask 1

暴力穷举区间,枚举众数判断即可。复杂度 O(n3)

Subtask 2

暴力穷举区间,区间元素个数用一个桶维护起来,区间 (i,j+1) 的各个元素的个数可以由区间 (i,j) 递推得。但由于 ai 得范围达到了 109,需要离散化一下。复杂度 O(n2)

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
int n,a[N],cop[N],tong[N];
ll ans=0;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>a[i],cop[i]=a[i];//cop为离散化数组
	sort(cop+1,cop+n+1);
	int cntl=unique(cop+1,cop+n+1)-cop-1;
	for(int i=1;i<=n;++i)
		a[i]=lower_bound(cop+1,cop+cntl+1,a[i])-cop;//离散化
	for(int l=1;l<=n;++l)
	{
		int cur=a[l];
		for(int r=l;r<=n;++r)
		{
			tong[a[r]]++;//更新桶数组
			if(tong[a[r]]>tong[cur])cur=a[r];//打擂台得出出现次数最多的元素
			if(tong[cur]*2>r-l+1)ans++;//判断区间众数个数一半
		}
		for(int r=l;r<=n;++r)tong[a[r]]--;//清空tong数组
	}
	cout<<ans;
	return 0;
}

Subtask 3

考虑到只有 12 两种数字,所以区间不合法只有一种情况:区间内 12 个数相等。考虑将 1 的值设成 12 的值设成 1,所以不合法的区间的区间和为 0,可以记录一个前缀桶来维护。复杂度 O(n)

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
int n,a[N],b[N],tong[N<<1];
ll ans=0;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		if(a[i]==1)b[i]=1;
		else b[i]=-1;
	}
	//因为前缀和会出现负数,所以要统一偏移n 
	int sum=0;tong[n]++;
	for(int i=1;i<=n;++i)
	{
		sum+=b[i];//前缀和 
		ans+=tong[sum+n];//更新答案 
		tong[sum+n]++;//更新桶 
	}
	cout<<1ll*n*(n+1)/2-ans;//合法区间-不合法区间 
	return 0;
}

Subtask 4

受 Subtask 3 的启发,穷举每个种类的数,分别计算贡献。

ai109 明显是纸老虎,离散化一下就可以了。考虑当前计算的是 x 的贡献,记 Si 为前 i 个数中 x 出现的次数,则 [l+1,r] 区间合法则需

2(SrSl)>rl

移一下项就变为

2Srr>2Sll

这样问题就变成了:对于每一个 r0r1 中由多少个 l 使 2Sll<2Srr,是一个二维偏序问题,可以通过树状数组维护,复杂度是 O(knlogn) 的,其中 k 为不同数字的个数。但是这样还是只能通过前三个 Subtask,需要优化。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
int n,a[N],cop[N],bsum[N<<1],s[N];
ll ans=0;
inline int lowbit(int x){return x&-x;}
inline void add(int i,int x)
{
	while(i<=2*n+2)bsum[i]+=x,i+=lowbit(i);
}
inline int query(int i)
{
	int res=0;
	while(i)res+=bsum[i],i-=lowbit(i);
	return res;
}
signed main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>a[i],cop[i]=a[i];//cop为离散化数组
	sort(cop+1,cop+n+1);
	int cntl=unique(cop+1,cop+n+1)-cop-1;
	for(int i=1;i<=n;++i)
		a[i]=lower_bound(cop+1,cop+cntl+1,a[i])-cop;//离散化
	for(int i=1;i<=cntl;++i)
	{
		for(int j=1;j<=n;++j)
			s[j]=s[j-1]+(a[j]==i);//求出前缀和
		add(n+1,1);//n+1的偏移量
		for(int j=1;j<=n;++j)
		{
			ans+=query(2*s[j]-j+n);//树状数组查询
			add(2*s[j]-j+n+1,1);
		}
		add(n+1,-1);
		for(int j=1;j<=n;++j)
			add(2*s[j]-j+n+1,-1);//清空树状数组
	}
	cout<<ans;
	return 0;
}

Bi=2Sii,对于每一个 x,考虑 Bi 的变化情况。举个例子:

可以发现如果有 mx,那么 Bi 可以被分成 m+1 个区间,每个区间都是一个公差为 1 的等差数列。考虑到所有的 m 的和为 n,我们其实只需要快速处理每一段里的数。

假设这个等差数列为 s,s1,,e+1,e,用一个数组 Ci 来记录每一个数的个数,那么就是 [e,s] 区间的每个 Ci 都加 1

Di 表示 Ci 的前缀和,即 Di=j=1iCj,对于每个 Bi,贡献即为 DBi1,所以对于 [e,s] 这个区间内的所有数,总贡献即为 i=e1s1Di,这又变成了一个区间和问题,我们再维护一个数组 Ei 表示 Ci 前缀和的前缀和,即为 Di 数组的前缀和,即 Ei=j=1iDj,这样总贡献就变成了

i=e1s1Di=Es1Ee2

至此问题就变成了一个区间修改,求二阶前缀和的问题。这就有很多解法了,这里只提供一个树状数组的解法。

考虑到区间修改可以通过单点修改,求前缀和来解决,问题就可已转化为单点修改,求三阶前缀和

推的过程比较繁琐,如果不理解可以自己手模两组数据来理解。


现在问题变成了单点修改,求三阶前缀和,该如何处理?

如果不会建议先看一看这道题,ABC 256 F Cumulative Cumulative Cumulative Sum,这里讲一下树状数组的思路。

先假设 Ai 为原数组,Bi 数组为 Ai 的前缀和,Ci 数组为 Ai 数组的二阶前缀和,Di 数组为 Ai 的三阶前缀和。

考虑 AiBx (ix) 的贡献为 1

考虑 AiCx (ix) 的贡献为 xi+1

考虑 AiDx (ix) 的贡献为 (xi+1)(xi+2)2

Di=i=1x(xi+1)(xi+2)2Ai

Di=(x+1)(x+2)2i=1xAi2x+32i=1xiAi+12i=1xi2Ai

用树状数组分别维护 Ai,iAi,i2Ai,可以解决。


至此,问题就完全解决了。代码如下,写了注释。

AC 代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
int n,a[N],cop[N];
ll ans=0,bs1[N<<1],bs2[N<<1],bs3[N<<1];
//bs1,bs2,bs3维护树状数组,需要进行n+1的偏移量 
vector<int>t[N];
inline int lowbit(int x){return x&-x;}
inline void add(int i,ll d)
{
	int x=i;
	while(i<=2*n+2)
	{
		bs1[i]+=d;bs2[i]+=d*x;bs3[i]+=d*x*x;
		i+=lowbit(i);
	}
}
inline ll query(int i)
{
	ll res=0;int x=i;
	while(i)
	{
		res+=bs1[i]*(x+2)*(x+1)-bs2[i]*(2*x+3)+bs3[i];
		i-=lowbit(i);
	}
	return res/2;
}
//树状数组 
signed main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
		cin>>a[i],cop[i]=a[i];//cop为离散化数组
	sort(cop+1,cop+n+1);
	int cntl=unique(cop+1,cop+n+1)-cop-1;
	for(int i=1;i<=n;++i)
		a[i]=lower_bound(cop+1,cop+cntl+1,a[i])-cop;//离散化
	for(int i=1;i<=n;++i)t[a[i]].push_back(i);//每一类数组又一个向量存起来 
	for(int i=1;i<=cntl;++i)//穷举数字 
	{
		t[i].push_back(n+1);
		int lst=0;
		for(int j=0;j<t[i].size();++j)
		{
			int y=2*j-lst+n+1,x=2*j-t[i][j]+n+2;
			ans+=query(y-1)-(x>=3?query(x-2):0);//树状数组查询 
			add(x,1);add(y+1,-1);lst=t[i][j];
		}
		lst=0;
		for(int j=0;j<t[i].size();++j)
		{
			int y=2*j-lst+n+1,x=2*j-t[i][j]+n+2;
			add(x,-1);add(y+1,1);lst=t[i][j];
		}//清空树状数组 
	}
	cout<<ans;
	return 0;
}
posted @   lnwhl  阅读(113)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示