CF1983C Have Your Cake and Eat It Too 题解

题意简述

n n n块蛋糕和三个小朋友Alice,Bob和Charlie,对于每个小朋友来说,每块蛋糕的价值是不同的。第 i i i块蛋糕对Alice,Bob,Charlie来说的价值分别是 a i a_i ai, b i b_i bi, c i c_i ci(其中, ∑ i = 1 n a i = ∑ i = 1 n b i = ∑ i = 1 n c i = t o t ) \sum_{i = 1}^{n}a_i=\sum_{i = 1}^{n}b_i=\sum_{i = 1}^{n}c_i=tot) i=1nai=i=1nbi=i=1nci=tot)。你要为他们三个各分配一段连续的区间 [ l , r ] [l,r] [l,r]中的蛋糕,要求三人分配到区间不相交,不重叠,并且每个人获得的区间中的蛋糕对Ta来说的价值总和大于等于 ⌈ t o t 3 ⌉ \lceil\frac{tot}{3}\rceil 3tot。输出区间时打印出每个人的区间的左右端点下标(下标从1开始),两个下标之间用一个空格隔开。如果没有符合要求的区间,输出-1

思路

(这里假设区间的顺序就是 a , b , c a,b,c a,b,c,在写程序时要考虑全排列,一共6种)设三个区间是 [ l 1 , r 1 ] [l_1,r_1] [l1,r1] [ l 2 , r 2 ] [l_2,r_2] [l2,r2] [ l 3 , r 3 ] ( r 1 < l 2 , r 2 < l 3 ) [l_3,r_3](r_1<l_2,r_2<l_3) [l3,r3](r1<l2,r2<l3)。由于每个数都非负,区间越大和就越大,也更可能满足和大于等于 ⌈ t o t 3 ⌉ \lceil\frac{tot}{3}\rceil 3tot的要求, l 1 = 1 l_1=1 l1=1 l 2 = r 1 + 1 l_2=r_1+1 l2=r1+1 l 3 = r 2 + 1 l_3=r_2+1 l3=r2+1 r 3 = n r_3=n r3=n显然最优。如果 r 1 = x r_1=x r1=x ∑ i = 1 x a i \sum_{i = 1}^{x}a_i i=1xai已经达到要求,则没必要再扩大第一个区间,第三个区间的 l 3 l_3 l3同理,剩下的则全部分配给第二个区间。
有了这样一个贪心思路,就可以开始实现了。用 s u m i j sum_{ij} sumij记录第 i i i个人的蛋糕价值的前缀和,从前往后遍历,记录符合要求(即前缀和大于等于 ⌈ t o t 3 ⌉ \lceil\frac{tot}{3}\rceil 3tot)的最小下标 p s 1 i ps1_i ps1i,从后往前遍历,记录符合要求(即后缀和大于等于 ⌈ t o t 3 ⌉ \lceil\frac{tot}{3}\rceil 3tot)的最大下标 p s 2 i ps2_i ps2i。接着枚举每一种排列三个小朋友的方法( i d i id_i idi记录第 i i i个是谁, i i i从0开始),判断中间的那个人获得的蛋糕价值之和是否符合要求(区间为 [ p s 1 i d 0 + 1 , p s 2 i d 2 − 1 ] [ps1_{id_{0}}+1,ps2_{id_{2}}-1] [ps1id0+1,ps2id21]),若符合要求,即可输出。
在这里插入图片描述

经验与教训

  • 十年OI一场空,不开long long见祖宗
  • s u m sum sum数组千万别用memset清空,会TLE,只用将第零个赋值成0就行
  • 最好用循环,不要把三个人的值分开来处理,会很烦。全排列时也最好用数组记录编号,用自带的next_permutation枚举全排列。
  • 输出的时候别忘了还原顺序,依次输出Alice,Bob和Charlie的区间(代码中使用了 a n s [ i ] . p s ans[i].ps ans[i].ps记录原本的编号,输出前按 p s ps ps排序)

代码

#include<bits/stdc++.h>
using namespace std;
int t,n,a[4][200005],ps1[4],ps2[4];
long long tot,sum[4][200005];
struct node{
	int ps,x,y;
} ans[3];
bool cmp(node aa,node bb){ return aa.ps<bb.ps; }
int main(){
	cin>>t;
	while(t--){
	    sum[1][0]=sum[2][0]=sum[3][0]=tot=0;
		cin>>n;
		for(int i=1;i<=3;i++)
			for(int j=1;j<=n;j++){
				cin>>a[i][j];
				if(i==1) tot+=a[i][j];
				sum[i][j]=sum[i][j-1]+a[i][j];
			}
		tot=(tot+2)/3;
		for(int i=1;i<=3;i++){
			for(int j=1;j<=n;j++)
				if(sum[i][j]>=tot){
					ps1[i]=j;
					break;
				}
			for(int j=n-1;j>=0;j--)
				if(sum[i][n]-sum[i][j]>=tot){
					ps2[i]=j+1;
					break;
				}
		}
		int id[3]={1,2,3};
		bool flag=0;
		do{
			for(int i=0;i<3;i++)
				ans[i].ps=id[i];
			ans[0].x=1,ans[0].y=ps1[id[0]];
			ans[1].x=ps1[id[0]]+1,ans[1].y=ps2[id[2]]-1;
			if(sum[id[1]][ans[1].y]-sum[id[1]][ans[1].x-1]<tot)
				continue;
			ans[2].x=ps2[id[2]],ans[2].y=n;
			sort(ans,ans+3,cmp);
			for(int i=0;i<3;i++)
				cout<<ans[i].x<<' '<<ans[i].y<<' ';
			cout<<endl;
			flag=1;
			break;
		}while(next_permutation(id,id+3));
		if(!flag) cout<<-1<<endl;
	}
	return 0;
}
  • 32
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值