DTOJ#3790. 蛋糕

在举办了打字竞赛和大扫除后,学校决定买蛋糕来犒劳学生。

学校派了一些学生去蛋糕店买蛋糕,宫水三叶是其中之一。

蛋糕店总共有 n n n 个蛋糕,学校决定买 m m m 个,并且把它们摆在操场上。

每一个蛋糕有两种价值 v i , c i v_i,c_i vi,ci。假设学校确定了每个蛋糕,那么获得的总价值为:

∑ i = 1 m ( v i − ∣ c i − c i + 1 ∣ ) \sum_{i=1}^{m} (v_i-|c_i-c_{i+1}|) i=1m(vicici+1)

其中 c m + 1 = c 1 c_{m+1}=c_{1} cm+1=c1

现在三叶知道了商店里 n n n 种蛋糕的 v i , c i v_i,c_i vi,ci,她需要确定一种选择 m m m 个蛋糕并把这些蛋糕排序的方案,使得上述价值最大。

形式化的说:给定 n n n 个整数对 v i , c i v_i,c_i vi,ci,你需要选择 m m m 个整数对并按照某种顺序排列,使得 ∑ i = 1 m ( v i − ∣ c i − c i + 1 ∣ ) \sum_{i=1}^{m} (v_i-|c_i-c_{i+1}|) i=1m(vicici+1) 最大。

第一行两个整数 n , m n,m n,m

接下来 n n n 行,每一行两个整数 v i , c i v_i,c_i vi,ci

一行一个整数,表示答案。

样例输入1
5 3
2 1
4 2
6 4
8 8
10 16
样例输出 1
6
样例解释 1

选择 1 , 3 , 2 1,3,2 1,3,2

样例输入 2
8 4
112103441 501365808
659752417 137957977
86280801 257419447
902409188 565237611
965602301 689654312
104535476 646977261
945132881 114821749
198700181 915994879
样例输出 2
2323231661

本题采用捆绑测试。

对于所有数据,满足 3 ≤ m ≤ n ≤ 2 × 1 0 5 , 1 ≤ v i , c i ≤ 1 0 9 3\le m \le n \le 2\times 10^5,1\le v_i,c_i \le 10^9 3mn2×105,1vi,ci109

子任务编号 n n n分值
1 1 1 ≤ 1 0 2 \le 10^2 102 5 5 5
2 2 2 ≤ 2 × 1 0 3 \le 2\times 10^3 2×103 19 19 19
3 3 3 ≤ 2 × 1 0 5 \le 2\times 10^5 2×105 76 76 76

题解:
首先,考虑我们已经有一个序列,则必须按照升序排序,贪心易证。
其次,若确定 c c c 最大值和最小值,那么 v v v 取前 m m m 大最优。
这时,我们考虑枚举最小值,发现最优决策的右端点对于左端点是单调递增的。
f [ i ] f[i] f[i] 表示以 c i c_i ci 为最小值,使得答案最优的右端点(最大值)。则 i ≤ j , f [ i ] ≤ f [ j ] i \leq j,f[i] \leq f[j] ij,f[i]f[j]
感性理解就是反证,若 f [ i ] > f [ j ] f[i] > f[j] f[i]>f[j],不如把 f [ i ] − > f [ j ] f[i]->f[j] f[i]>f[j],这样答案合法且更优。
这样就可以考虑分治。
每次把左端点的区间二分,暴力计算 f [ m i d ] f[mid] f[mid],分治下去时就可以 [ l , m i d ] [l,mid] [l,mid] 只考虑右端点 [ 1 , f [ m i d ] ] [1,f[mid]] [1,f[mid]] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 同理。
时间复杂度:分治 l o g log log 层,每层总和为 n n n,所以总为 O ( n l o g n ) O(nlogn) O(nlogn)

#include<bits/stdc++.h>
#define N 200005
typedef long long ll;
using namespace std;
inline int read(){
	int x=0,f=1;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
	return x*f;
}
ll v[N],c[N],b[N],bt,n,m;
struct node{
	int v,c;
}pre[N];
bool cmp(node a,node b){return a.c<b.c;}
struct seg{
	int ls,rs,cnt;
	ll sum;
}t[N<<7];
int tot,root[N];
ll ans[N];
inline int get(int x){return lower_bound(b+1,b+1+bt,x)-b;}
inline int add(){
	t[++tot].ls=t[tot].rs=t[tot].sum=t[tot].cnt=0;
	return tot;
}
int change(int p,int q,int l,int r,int tl){
	t[p]=t[q];
	if(l==r){
		t[p].cnt++;t[p].sum+=b[tl];
		return p;
	}
	int mid=(l+r)>>1;
	if(tl<=mid)t[p].ls=change(add(),t[q].ls,l,mid,tl);
	else t[p].rs=change(add(),t[q].rs,mid+1,r,tl);
	t[p].sum=t[t[p].ls].sum+t[t[p].rs].sum;
	t[p].cnt=t[t[p].ls].cnt+t[t[p].rs].cnt;
	return p;
}
ll ask(int q,int p,int l,int r,int k){
	if(t[p].cnt-t[q].cnt<k)return -1;
	if(k==0)return 0;
	if(l==r){
		return k*b[l];
	}
	int mid=(l+r)>>1,rcnt=t[t[p].rs].cnt-t[t[q].rs].cnt;
	ll sum=0;
	if(k>=rcnt)sum+=ask(t[q].ls,t[p].ls,l,mid,k-rcnt)+t[t[p].rs].sum-t[t[q].rs].sum;
	else sum+=ask(t[q].rs,t[p].rs,mid+1,r,k);
	return sum;
}
void solve(int l,int r,int pl,int pr){
	if(l>r)return ;
	if(l==r&&ans[l])return ;
	int mid=(l+r)>>1,pos=n;
	ans[mid]=-1e15;
	for(int i=max(mid,pl);i<=pr;++i){
		ll now=ask(root[mid],root[i-1],1,bt,m-2);
		
		if(now==-1)continue;
		now=v[mid]+v[i]+now-1ll*2*(c[i]-c[mid]);
		if(ans[mid]<now)pos=i;
		ans[mid]=max(ans[mid],now);
	}
	solve(l,mid-1,pl,pos);solve(mid+1,r,pos,pr);
}
int main(){
//	freopen("data.in","r",stdin);
	n=read(),m=read();
	tot=-1;add();
	for(int i=1;i<=n;++i)v[i]=read(),c[i]=read(),pre[i].v=v[i],pre[i].c=c[i],b[++bt]=v[i];
	sort(pre+1,pre+1+n,cmp);
	sort(b+1,b+1+bt);
	bt=unique(b+1,b+1+bt)-b-1;
	root[0]=0;
	for(int i=1;i<=n;++i){
		v[i]=pre[i].v;c[i]=pre[i].c;
		root[i]=change(add(),root[i-1],1,bt,get(v[i]));
	}
	solve(1,n,1,n);
	ll maxn=-1e15;
	for(int i=1;i<=n;++i)maxn=max(maxn,ans[i]);
	printf("%lld\n",maxn);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值