HDOJ 6609 Find the answer

本文解析一道算法竞赛题目,探讨如何在给定序列中选择最小数量的元素,通过修改这些元素使序列总和不超过指定值。介绍了一种高效算法,使用单调数组和二分查找来减少计算量,确保在限定时间内解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接
Problem Description
Given a sequence of n integers called W and an integer m. For each i (1 <= i <= n), you can choose some elements Wk (1 <= k < i), and change them to zero to make ∑ij=1Wj<=m. So what’s the minimum number of chosen elements to meet the requirements above?.

Input
The first line contains an integer Q — the number of test cases.
For each test case:
The first line contains two integers n and m — n represents the number of elemens in sequence W and m is as described above.
The second line contains n integers, which means the sequence W.

1 <= Q <= 15
1 <= n <= 2*105
1 <= m <= 109
For each i, 1 <= Wi <= m

Output
For each test case, you should output n integers in one line: i-th integer means the minimum number of chosen elements Wk (1 <= k < i), and change them to zero to make ∑ij=1Wj<=m.

Sample Input
2
7 15
1 2 3 4 5 6 7
5 100
80 40 40 40 60

Sample Output
0 0 0 0 0 2 3
0 1 1 2 3

  • 吐槽
    疯狂pe,一个ans后面加一个空格?
  • 题意
  • 思路
    对于第i个位置,怎样选择数字才会使满足条件情况下选择数字数目最少呢?很容易想到,需要选择前i1个数中较大的数字,使其变为0 基于这个思想,如果我们对于每个位置i都暴力去找最大的前几个数,显然会TLE! 可以注意到,题目可以转化为前i-1个数中最多选出多少个数字和W[i]相加使得其和小于等于m(很容易 想到,选择较小的数才会使选的数最多)//这段抄的题解因为我也是这么想的
    我们对给定数组进行离散化,对于离散化之后的数组建立一颗线段树,线段树上的每个节点记录区间之 和以及区间内数字个数。 时间复杂度:Nlog(N)//这是题解的做法
    但这样搞代码量有点大啊,下面给一种不要建树的方法
    维护两个单调的数组,q[maxn],sum[maxn],q代表前i-1个数中的数的单调递增的序列,sum代表q数组的前缀和,且sum<=m。(也就是说只维护前k个和小于m的数),对每个w,二分查找sum中最后一个小于等于m-w的数,等到答案,在再判断需不需要插入w,需要的话二分插入。有人说这是假算法。分析下,其实这并不是假算法,对于sum数组,考虑数组的最大长度,当数组的最后一个值为m时无法再增长,且要使每次插入w都从第一个插入,所以q内的数各不相同,故数组最大长度为 1 e 9 \sqrt{1e9} 1e9 为32000左右.事实上不会每次插入达到这个长度,从0开始,故最多的计算次数大约为3e9,题目给的时限为4秒,我们认为1秒的计算量为1e9显然卡不掉。
  • 代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int t,n,m,cnt,w;
int q[200005]={0};
int sum[200005];
int fd(int l,int r,int x,int *s){
	while(l<r){
		int mid=(l+r+1)>>1;
		if(s[mid] <= x) l=mid;
		else r=mid-1;
 	}
 	return l;
}
int main(){
	scanf("%d",&t);
	while(t--){
		memset(sum,0,sizeof(sum));
		cnt=1;
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;i++){
			scanf("%d",&w);
			int dt=m-w;
			if(sum[cnt-1]<=dt){
				int j=fd(0,cnt-1,w,q);
				for(int k=cnt;k>j+1;k--){
					q[k]=q[k-1];
					sum[k]=sum[k-1]+w;
				}
				cnt++;
				q[j+1]=w;
				sum[j+1]=sum[j]+w;
				printf("%d ",i-cnt+2);
			}else{
				int j=fd(0,cnt-1,dt,sum);
				printf("%d ",i-j);
				if(q[cnt-1]>w){
					int j=fd(0,cnt-1,w,q);
				for(int k=cnt;k>j+1;k--){
					q[k]=q[k-1];
					sum[k]=sum[k-1]+w;
					}
					q[j+1]=w;
					sum[j+1]=sum[j]+w;
				}
			}
		}
		printf("\n");
	}
}

与题解对比我的时间还少了一倍(哈哈,应该是数据的问题)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值