合并胡萝卜堆

#####学习记录自用,欢迎批评指正

题目:

前情提要:小白兔在台阶上发现了亿堆胡萝卜,于是他又登上了台阶打算搬运一点胡萝卜。

但是,当他来到台阶顶时,发现胡萝卜都散乱的分成了好多堆,于是小白兔决定把所有的胡萝卜合并成一堆胡萝卜。但由于小白兔太小了,一次合并只能合并两堆胡萝卜。由于小白兔在合并完成之后,还要搬运一些胡萝卜回到幼稚园,所以他需要节省体力。那么如何合并所有胡萝卜堆可以使消耗的体力最小?

假设每个胡萝卜重量均为1,每堆胡萝卜的数量即为重量,消耗的体力与重量成正比,即,可以假定合并重量为1和3的胡萝卜堆消耗的体力为1+3 = 4

输入格式

输入包括两行,第一行是一个整数n,表示胡萝卜堆的堆数。

第二行包含n个整数,第i个整数 a[i]是第 i堆胡萝卜堆的数目。

n -> [1, 10^5]

a[i] -> [1, 10^9]

输出

输出包括一行,这一行只包含一个整数,即最小的体力耗费值。
只能使用C语言

测试输入期待的输出时间限制内存限制额外进程
测试用例 1以文本方式显示
  1. 3↵
  2. 1 2 9↵
以文本方式显示
  1. 15↵
1秒1024KB0

思路:

这个题在洛谷其实是有原型的:基础版P1090提升版P6033

但是洛谷的题解不少都用了C++的方法,学校的作业系统只允许使用C语言。 

这里记录一下我的思路struggle历程。

以下是我在学校讨论区发布的内容:

整个题的思路就和之前的同学说的那样很简单,每次取最小值和次小值进行合并就可以。

但在如此大的数据量和数据大小之下怎么让程序有更高的效率、更快的速度是个问题。

所以最初我用了链表的方式,我的想法是,使用链表的话只需要在第一次进行数组排序,然后建立链表,在之后的合并过程中,合并后的元素只需要通过对之后元素的查找来按顺序插入就好,如果用数组的话数字的移动肯定会导致超时,但链表不需要移动,所以会更加节省时间。

但是事实是编译后有很多很多TLE…………

百度后理由如下:

结论:数组顺序遍历快于链表

原因:CPU每次取数据时都不是只取一个数据,而是将那连续的一块一起取,而因为数组的地址空间是连续的,所以下几次要访问的元素也一起被放入缓存中了,减少了对内存的访问(访问速度:缓存 快于 内存)。由于链表的元素是离散的放在内存中的,所以遍历每个元素的都需要对内存进行访问,导致顺序访问效率不如数组

原文链接:

https://blog.csdn.net/qq_45843451/article/details/124216812

OK,fine.我重写。
 

然后老老实实用数组写。这次的思路是不每次都进行排序,每次都只搜索当前数组中最小和次小的两个数字进行合并。一定程度上比每次都进行重新排序要快得多。但结果是最后三个TLE。

OK,fine.我再重写。

然后本蒟蒻参照了讨论区的方法——开两个数组,一个记录原始值,一个记录合并之后的新值的方法,进行比较,选取两个最小的进行合并的方法,最终AC了。但在这个过程中也有一些小插曲——我尝试着使用了桶排序的方法(不得不说还是很好用的)对数组进行第一次排序,但是可能是因为开的数组过大了(因为数据值过大, 要求数组的大小也大),这次直接全是TLE了。

总之就是很曲折,很曲折,方法要选对,要不然就是白搭。

这个题思路还是很明白的,在这儿就没必要重新梳理思路了,直接上代码吧。当然,最后AC的代码就不放出来了,毕竟这个课的代码要查重的。

首先是冤种链表方法:(这个方法在洛谷的P1090题提交是可以AC的,但是P6033就会超时,在作业提交系统中也会超时)

#include <stdio.h>
#include <stdlib.h>
//#include <time.h>
//double start=clock();
long long a[100001]={0},energy=0;
typedef struct link  
{  
	long long num;  
	struct link *next;  
}NODE; 
int cmp(const void *a,const void *b){
	long long n1=*(long long*)a;
	long long n2=*(long long*)b;
	return (n1-n2);
}
void setlink(NODE *h,int n)
{
	NODE *p=NULL, *q=NULL;  
	int i,num;  
	for( i=1; i<=n; i++)  
	{  
		p = (NODE *)malloc(sizeof(NODE));  
		p->num = a[i];  
		p->next = NULL;  
		num=p->num;
		if( h->next == NULL )  
		{  
			h->next = p;  
			q = p;  
		}  
		else  
		{  
			q->next = p;  
			q = q->next;  
		}  
	}  
	p->next=NULL;
	/*printf("最初的链表");
	for(p=h->next;p!=NULL;p=p->next)printf("%lld ",p->num);
	printf("\n");
	*/
	return;  
}
void sum(NODE *head)
{
	NODE *p=head->next,*q=head->next->next;
	q->num+=p->num;
	energy+=q->num;
	head->next=p->next;
	/*printf("sum运行");
	for(p=head->next;p!=NULL;p=p->next)printf("%lld ",p->num);
	printf("\n");
	printf("时间%f",clock()*/
	free(p);
	
}
void change(NODE *head)
{
	NODE *p=head->next,*q=head;
	int insert=0;
	if(head->next->next->next==NULL){
		p->next->num+=p->num;
		energy+=p->next->num;
		head->next=p->next;
		q=p->next;
		free(p);
		goto k;
	}
	if(p->num<=p->next->num)goto k;
	else{
		for(q=p->next;q->next->next!=NULL;q=q->next){
			if(p->num >= q->num&&p->num <= q->next->num){
				head->next=p->next;
				p->next=q->next;
				q->next=p;
				insert=1;
				break;
			}
		}
	}
	
	if(insert==0)
	{
		for(q=p->next;q->next!=NULL;q=q->next);
		head->next=p->next;
		q->next=p;
		p->next=NULL;
	}
	k:;
	/*printf("change运行");
	for(p=head->next;p!=NULL;p=p->next)printf("%lld ",p->num);
	printf("\n");
	return;*/
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	if(n==1){
		printf("0\n");
		return 0;
	}
	else if(n==2){
		printf("%lld\n",a[1]+a[2]);
		return 0;
	}
	qsort(a,n+1,sizeof(long long),cmp);
	/*for(int i=1;i<=n;i++)
	{=
		printf("%lld ",a[i]);
	}*/
	NODE *head=NULL, *q=NULL;  
	head = (NODE *)malloc(sizeof(NODE));  
	head->num = -1;  
	head->next = NULL;  
	setlink(head, n);
	while(head->next->next!=NULL){
		sum(head);
		change(head);
	}
	printf("%lld\n",energy);
	return 0;
}

 接下来是寻找最小值和次小值的方法:(这个代码也可以在基础版AC,但进阶版仍然过不了)


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
long long a[100001]={0},energy=0;
int n;
void find(int start)
{
	int num=start;long long t;
	for(int i=start;i<=n;i++){
		if(a[i]<a[num])num=i;
	}
	t=a[num];a[num]=a[start];a[start]=t;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	if(n==1){
		printf("0\n");
		return 0;
	}
	else if(n==2){
		printf("%lld\n",a[1]+a[2]);
		return 0;
	}
	find(1);
	find(2);
	for(int i = 2; i <= n ;i++)
	{
		a[i]+=a[i-1];
		energy+=a[i];
		find(i);
		find(i+1);
	}
	printf("%lld\n",energy);
	return 0;
}

 最后提到的桶排序的方法就不在这里赘述了,洛谷的题解中有详细解释。

以上贴出的两个代码如果有大佬能够优化欢迎指点~

AC代码不放出了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值