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;
}

浙公网安备 33010602011771号