试题 算法训练 礼物

文章介绍了一种利用前缀和和双指针优化的算法来解决取石子问题,强调了在资源限制下的高效求解策略。
摘要由CSDN通过智能技术生成

资源限制

内存限制:256.0MB   C/C++时间限制:1.0s   Java时间限制:3.0s   Python时间限制:5.0s

问题描述

  JiaoShou在爱琳大陆的旅行完毕,即将回家,为了纪念这次旅行,他决定带回一些礼物给好朋友。
  在走出了怪物森林以后,JiaoShou看到了排成一排的N个石子。
  这些石子很漂亮,JiaoShou决定以此为礼物。
  但是这N个石子被施加了一种特殊的魔法。
  如果要取走石子,必须按照以下的规则去取。
  每次必须取连续的2*K个石子,并且满足前K个石子的重量和小于等于S,后K个石子的重量和小于等于S。
  由于时间紧迫,Jiaoshou只能取一次。
  现在JiaoShou找到了聪明的你,问他最多可以带走多少个石子。

输入格式

  第一行两个整数N、S。
  第二行N个整数,用空格隔开,表示每个石子的重量。

输出格式

  第一行输出一个数表示JiaoShou最多能取走多少个石子。

样列输入

  8 3
  1 1 1 1 1 1 1 1

样列输出

  6

样列解释

  任意选择连续的6个1即可。

数据规模和约定

  对于20%的数据:N<=1000
  对于70%的数据:N<=100,000
  对于100%的数据:N<=1000,000,S<=10^12,每个石子的重量小于等于10^9,且非负

思路:

这道题的数据量很多而且数据很大,两层循环就得爆掉了,所以得考虑优化到线性或者log才有可能成功,我本来的想法是用前缀和,这样可以减少区间加的运算,但是到后边求石子数时因为区间长度不定,所以还是要套上两层循环,于是爆了。

在网上搜索然后发现一个网友的思路很好,他设置了两个数组,一个l[],一个r[],其中l[i]为以i位置为基准,向前边能扩展的最多石子数,如1,2,3时,s=3时,显然1+2=3,则l[1](从0开始)为2,因为可扩展0,1位置的石子。同理,r[i]是以i位置为基准,向后边能扩展的最多石子数。利用双指针可以将求l和r数组的时间复杂度优化成O(n)。

最后一步,一层循环遍历,只要求出最大的2*min(l[i],r[i+1])即为所求,很好理解,左边与右边同时扩展,扩展的数的最小值才是整体的值。r的位置为i+1,不能为i,为i的话与l[i]就有数字重叠了,不合题意。

不过看题目,好像用二分也可以试试,但得加上前缀和优化。就是设置边界l,r,以mid位置为基准,前缀和相减判断l到mid和mid+1到r是否符合标准,不符合就将边界减小。具体我也没细想,可以参考网友的想法:http://t.csdnimg.cn/zvDuR

代码如下:

标准代码:

#include<bits/stdc++.h>
using namespace std;
long long a[1000010];
int l[1000010],r[1000010];
int main(){
	long long n,s;
ios::sync_with_stdio(false);//取消输入输出缓存,加快cin、cout运算时间,这是个极大的优化
	cin>>n>>s;
	for(int i=0;i<n;i++)cin>>a[i];
	//求离左边最符合的石子数 
	long long sum1=a[0];
	for(int i=0,j=0;j<n;){
		if(sum1>s){
			sum1-=a[i];
			i++;
		}
		else if(sum1<=s){
			l[j]=j-i+1;
			j++;
			sum1+=a[j];
		}
	}
	
	//求离右边最符合的石子数 
	long long sum2=a[n-1];
	for(int i=n-1,j=n-1;j>=0;){
		if(sum2>s){
			sum2-=a[i];
			i--;
		}
		else if(sum2<=s){
			r[j]=i-j+1;
			j--;
			sum2+=a[j];
		}
	}
	int maxNum=0;
	for(int i=0;i<n-1;i++){
		maxNum=max(maxNum,2*min(l[i],r[i+1]));
	}
	cout<<maxNum;
	return 0;
}

第一次做时的前缀和方法代码(有错):

#include<bits/stdc++.h>
using namespace std;
long long pre[1000001];
int a[1000001];
int main(){
	long long n,s;
	cin>>n>>s;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pre[i]=pre[i-1]+a[i];
	}
	for(int i=n/2;i>=0;i--){
		for(int j=i;j<n-i;j++){
			if(pre[j]<=s&&pre[i+j]-pre[j]<=s){
				cout<<i*2;
				return 0;
			}
		}
	}
	cout<<0<<endl;
	return 0;
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值