洛谷 P8313 [COCI2021-2022#4] Izbori

PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接:洛谷 P8313 [COCI2021-2022#4] Izbori

题目

题目描述

Malnar 先生正在竞选县长,这个县一共有 n n n 栋房屋,每栋房屋里都住着一位居民。Malnar 先生知道,选举的赢家不一定是最好的候选人,而是在选举前举办的宴会最好的候选人。因此,在选举前几天,他将邀请第 l l l r ( l ≤ r ) r(l\le r) r(lr) 栋房屋内居住的居民,为他们准备一顿丰盛的晚餐。

Malnar 先生知道所有居民最喜欢吃的菜。在宴会上,他会准备大多数人喜欢的一道菜。如果一个人吃到了自己最喜欢吃的菜,将会投一票给 Malnar 先生。但是如果没有吃到自己最喜欢吃的菜,他们将会把票投给 Vlado 先生。如果没有来参加晚宴的居民,他们将会忘记选举,不做出任何投票。如果一个候选人获得了投了票的人中一半以上的人的支持,他将会赢得竞选。

Malnar 先生想知道,有多少组的 ( l , r ) (l,r) (l,r) 可以使他赢得竞选。

输入格式

第一行包含一个整数 n n n,表示房屋的数量。

第二行包含 n n n 个整数 a i a_i ai,表示第 i i i 栋房屋内居民最喜欢的菜。

输出格式

仅一行,输出可以使 Malnar 先生赢得竞选的 ( l , r ) (l,r) (l,r) 的组数。

样例 #1

样例输入 #1

2
1 1

样例输出 #1

3

样例 #2

样例输入 #2

3
2 1 2

样例输出 #2

4

样例 #3

样例输入 #3

5
2 2 1 2 3

样例输出 #3

10

提示

【样例 2 解释】

可以使 Malnar 先生赢得竞选的 ( l , r ) (l,r) (l,r) 为: ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 1 , 3 ) (1, 1),(2, 2),(3, 3),(1, 3) (1,1),(2,2),(3,3),(1,3)

【数据规模与约定】

本题采用子任务捆绑测试。

  • Subtask 1(10 pts): 1 ≤ n ≤ 300 1 ≤ n ≤ 300 1n300
  • Subtask 2(15 pts): 1 ≤ n ≤ 2 × 1 0 3 1 ≤ n ≤ 2\times10^3 1n2×103
  • Subtask 3(15 pts): ∀ i ∈ { 1 , 2 , 3 , … , n } , 1 ≤ a i ≤ 2 \forall i\in\{1,2,3,\dots,n\},1 ≤ a_i ≤ 2 i{1,2,3,,n},1ai2
  • Subtask 4(70 pts):没有额外限制。

对于 100 % 100\% 100% 的数据, 1 ≤ l ≤ r ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 1 0 9 1 \le l\le r ≤ n ≤ 2\times10^5,1 ≤ a_i ≤ 10^9 1lrn2×105,1ai109

【提示与说明】

本题分值按 COCI 原题设置,满分 110 110 110

题目译自 COCI2021-2022 CONTEST #4 T3 Izbori。

题解

25pts

O ( n 3 ) O(n^3) O(n3)的暴力
直接暴力累加计算就好了。
但其实累加的时候会有一个想法: s u m j − ( j > > 1 ) > s u m i − 1 − ( ( i − 1 ) > > 1 ) sum_{j}-(j>>1)>sum_{i-1}-((i-1)>>1) sumj(j>>1)>sumi1((i1)>>1) 即 2 × s u m j − j > 2 × s u m i − 1 − i − 1 即2\times sum_{j}-j>2\times sum_{i-1}-i-1 2×sumjj>2×sumi1i1
这就推出了我们的正解的想法。

110pts

我们再用一个数组 c c c来保存 2 × s u m i − i 2\times sum_i-i 2×sumii的值。
c i = 2 × s u m i − i c_i=2\times sum_i-i ci=2×sumii
因为事实上我们只需要 c c c的相对大小,并且每一段的 c c c都是一段公差为 1 1 1的等差数列,所以可以用类似于桶的想法,用一个数组 d d d来保存不同值的 c c c的个数。
d i = Σ j = 1 i c j d_i=\Sigma_{j=1}^ic_j di=Σj=1icj
于是我们就又会发现,对于一段 c c c,产生的贡献又是对应这一段的 Σ d i \Sigma d_i Σdi,所以可以再用数组 e e e保存数组 d d d的前缀和。
e i = Σ j = 1 i d j e_i=\Sigma_ {j=1}^id_j ei=Σj=1idj
同时我们需要进行区间修改并求前缀和,所以我们又可以将其改为单点修改并求前缀和,此时,我们还需要一个数组 f f f

总结一下上面的数组:(分不同种类的数)
s u m j = Σ i = 1 j a i sum_j=\Sigma_{i=1}^ja_i sumj=Σi=1jai
c j = 2 × s u m j − j c_j=2\times sum_j-j cj=2×sumjj
d k = Σ j = 1 n ( c j = = k ) (需要被区间修改) d_k=\Sigma_{j=1}^n(c_j==k)(需要被区间修改) dk=Σj=1n(cj==k)(需要被区间修改)
e i = Σ k = 1 i d k e_i=\Sigma_{k=1}^id_k ei=Σk=1idk
f l = Σ l = 1 i e l f_l=\Sigma_{l=1}^ie_l fl=Σl=1iel
所以维护三个树状数组就行。

代码实现

25pts

//洛谷 P8313 [COCI2021-2022#4] Izbori
#pragma GCC optimize(3)
#include
#include
#include
#include
using namespace std;
int n;
int a[200010];
int b[200010];
int sum[200010];
int cnt;
int x;
long long ans;

int find(int x){//二分查找
	int l=0,r=cnt,mid;
	while(l<r){
		mid=(l+r+1)>>1;
		if(b[mid]<=x){
			l=mid;
		}
		else{
			r=mid-1;
		}
	}
	return l;
}

void in(int &x){
	int nt;
	x=0;
	while(!isdigit(nt=getchar()));
	x=nt^'0';
	while(isdigit(nt=getchar())){
		x=(x<<3)+(x<<1)+(nt^'0');
	}
}

int main(){
	register int i,j;
	in(n);
	for(i=1;i<=n;++i){
		in(a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	cnt=unique(b+1,b+n+1)-(b+1);//离散化
	for(i=1;i<=n;++i){
		x=find(a[i]);
		++sum[x];
		++ans;
		for(j=i+1;j<=n;++j){
			x=++sum[find(a[j])]>sum[x]?find(a[j]):x;
			if(sum[x]>=((j-i+1)>>1)+1){
				++ans;
			}
		}
		memset(sum,0,sizeof(sum));
	}
	printf("%lld\n",ans);
	return 0;
}

110pts

//洛谷 P8313 [COCI2021-2022#4] Izbori
#pragma GCC optimize(3)
#include
#include
#include
#include
#include
using namespace std;
int n;
int a[200010];
int b[200010];//用于离散化 
long long d[400010];
long long e[400010];
long long f[400010];
int cnt;
int x,y;
long long ans;
vector<int>t[200010];

int lowbit(int x){
	return x&-x;
}

int find(int x){
	int l=0,r=cnt,mid;
	while(l<r){
		mid=(l+r+1)>>1;
		if(b[mid]<=x){
			l=mid;
		}
		else{
			r=mid-1;
		}
	}
	return l;
}

void in(int &x){
	int nt;
	x=0;
	while(!isdigit(nt=getchar()));
	x=nt^'0';
	while(isdigit(nt=getchar())){
		x=(x<<3)+(x<<1)+(nt^'0');
	}
}

void add(int i,long long c){
	int x=i;
	while(i<=2*n+2){
		d[i]+=c;
		e[i]+=c*x;
		f[i]+=c*x*x;
		i+=lowbit(i);
	}
}

long long query(int i){
	long long ans=0;
	int x=i;
	while(i){
		ans+=d[i]*(x+2)*(x+1)-e[i]*(2*x+3)+f[i];
		i-=lowbit(i);
	}
	return ans/2;
}

int main(){
	register int i,j,l;
	in(n);
	for(i=1;i<=n;++i){
		in(a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	cnt=unique(b+1,b+n+1)-(b+1);
	for(i=1;i<=n;++i){
		t[find(a[i])].push_back(i);
	}
	for(i=1;i<=cnt;++i){
		t[i].push_back(n+1);
		l=0;
		for(j=0;j<t[i].size();++j){
			y=2*j-l+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);
			l=t[i][j];
		}
		l=0;
		for(int j=0;j<t[i].size();++j){
			y=2*j-l+n+1;
			x=2*j-t[i][j]+n+2;
			add(x,-1);
			add(y+1,1);
			l=t[i][j];
		}
	}
	printf("%lld\n",ans);
	return 0;
}

你可能感兴趣的:(数据结构,树状数组)