CF276C Little Girl and Maximum Sum 题解

题目地址

简述题意:

给定一个序列 \(a\)\(q\) 次询问,可以将序列重新排列,求排列后每次询问的总和的最大值。

思路:

贪心+线段树/树状数组
我们可以记录每个点被区间覆盖的次数作为它的权重(记为 \(q[i]\))(这时要用到线段树/树状数组的区间修改),则询问的总和为:

\[\sum_{i=1}^{n} q[i]*a[i] \]

显而易见,要让大的 \(q[i]\) 配上大的 \(a[i]\),这样值才可能最大。

举个例子:\(4*5+3*2>4*2+3*5\)
再举个例子:
图

在这个情况下,我们就要 \(4\) 配上大的 \(a[i]\),然后再把 \(3\) 配上较大的 \(a[i]\),依次类推,\(1\) 陪到的就是最小的几个 \(a[i]\) 了。

所以我们可以将序列和每个点的权重排序(线段树/树状数组要先查询并记录每个点的值),让权重大的点配到尽可能大的数,最后再计算最大值即可。

线段树代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,l,r; 
long long a[114514*3],b[114514*3];//b保存线段树每个点的权重
long long ans=0;
inline int read(){
	int x=0,f=1;char ch;
	ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
inline void write(int x) {
  static int st[35];
  int top=0;
  do{
    st[top++]=x%10,x/=10;
  }while(x);
  while(top)putchar(st[--top]+48);
}//快读快写 
struct stree{
	long long l,r;
	long long sum,tag;
}t[114514*8];//线段树 
void build(int x,int l,int r){
	t[x].l=l,t[x].r=r;//传递区间[l,r] 
	if(l==r){ 
		t[x].sum=a[l];
		return;
	}//此点如是叶子节点则结束递归 
	int mid=(l+r)>>1;//区间中点 
	build(x*2,l,mid);//构造左子树 
	build(x*2+1,mid+1,r);//构造右子树 
	t[x].sum=t[x*2].sum+t[x*2+1].sum;//从下往上传值
}
void down(int x){
  	if(t[x].tag){//如果有标记 
  		t[x*2].tag+=t[x].tag;//下传左子树 
  		t[x*2+1].tag+=t[x].tag;//下传右子树 
  		t[x*2].sum+=(t[x*2].r-t[x*2].l+1)*t[x].tag;//左子树和增加 
		t[x*2+1].sum+=(t[x*2+1].r-t[x*2+1].l+1)*t[x].tag;//右子树和增加 
  		t[x].tag=0;//清空标记 
  	}
}
void change(int x,int l,int r,int a){
	if(t[x].l>=l&&t[x].r<=r){//完全包含 
		t[x].tag+=a;//标记区间 
		t[x].sum+=(t[x].r-t[x].l+1)*a;//区间 和增加 
		return;
	}
	if(t[x].l==t[x].r)return;//到叶子节点直接返回 
	down(x);//这时还没有操作就需下传标记 
	int mid=(t[x].l+t[x].r)>>1;//区间中点 
	if(mid>=l)change(x*2,l,r,a);//访问左半部分 
	if(mid<r)change(x*2+1,l,r,a);//访问右半部分
	t[x].sum=t[x*2].sum+t[x*2+1].sum;//刷新值
}
int ask(int x,int l,int r){
	if(l<=t[x].l&&r>=t[x].r)return t[x].sum;//完全包含 
	down(x);//只多了一个下传标记 
	int mid=(t[x].l+t[x].r)>>1;//区间中点 
	int sum=0; 
	if(mid>=l)sum+=ask(x*2,l,r);//访问左半部分 
	if(mid<r)sum+=ask(x*2+1,l,r);//访问右半部分 
	return sum;
}
signed main(){
	n=read();
	m=read(); 
	build(1,1,n);//建树 
	for(int i=1;i<=n;i++){
		a[i]=read();
		//add(i,0);
	}
	sort(a+1,a+n+1);//a排序 
	for(int i=1;i<=m;i++){
		cin>>l>>r;
		change(1,l,r,1);//权重增加 
	}
	for(int i=1;i<=n;i++){
		b[i]=ask(1,i,i);//记录权重,方便排序	
	}
	sort(b+1,b+1+n);//b排序
	for(int i=1;i<=n;i++){
		ans+=b[i]*a[i];//计算最大值 
	}	
	write(ans);
 	return 0;
}

树状数组代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,l,r; 
long long a[114514*2],b[114514*2];
long long ans=0,t[1919810];
inline int read(){
    int x=0,f=1;char ch;
    ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
inline void write(int x) {
  static int st[35];
  int top=0;
  do{
    st[top++]=x%10,x/=10;
  }while(x);
  while(top)putchar(st[--top]+48);
}//快读快写 
int add(int x,int y){
	while(x<=n){
		t[x]+=y;
		x+=x&-x;	
	}
}//修改/加点 
int ask(int x){
	int val=0;
	while(x>0){
		val+=t[x];
		x-=x&-x;	
	}
	return val;
}//查询 
signed main(){
	n=read();
	m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		add(i,0);//树状数组加点
	}
	sort(a+1,a+n+1);//a排序
	for(int i=1;i<=m;i++){
		cin>>l>>r;
		add(l,1);
		add(r+1,-1);//权重增加(树状数组修改) 
	}
	for(int i=1;i<=n;i++){
		b[i]=ask(i);	
	}
	sort(b+1,b+1+n);//b排序
	for(int i=1;i<=n;i++){
		ans+=b[i]*a[i];	//计算最大值
	}
	write(ans);
 	return 0;
}
posted @ 2023-07-20 15:15  ccrui  阅读(21)  评论(0)    收藏  举报