P1090合并果子

题目传送https://www.luogu.org/problem/show?pid=1090

这道水题有很多种做法是显然的,,,,在这里的就介绍一下我的几种做法吧

法一:

先排序,取两个最小的相加加入数组,再排序。。。

(这种做法谁都想得到吧,,,然后显然超时)

法二:

事先不是已经排过一遍序了吗?

那我把合并好了的那个数插入到数组中合适的位置不就ok了吗?

代码:

#include
#define r(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int a[10001],n,i,t,ans;
int main()
{
    scanf("%d",&n);
    r(i,1,n)    scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    r(i,1,n-1)
    {
        a[i+1]+=a[i];
        ans+=a[i+1];
        t=i+1;
        while(ta[t+1])
            swap(a[t],a[t+1]),t++;
    }
    printf("%d",ans);
return 0;
}
这个做法虽然不会超时(对于洛谷的数据,最大的300ms左右),但是效率还是比较低的。

法三:

想必优先队列都知道吧(不会的点这里http://blog.csdn.net/morewindows/article/details/6976468)

既然知道优先队列岂不是很简单了?

从优先队列里拿出最小的两个,然后合并再插入队列。

因为本人比较懒,所以就没写什么priority_queue,greater >q;了,直接插入负值就可以(priority_queueq;这样写是默认是降序的),然后最后再取ans的相反数就ok了

#include
#include
using namespace std;
priority_queueq;
int n,i,a,b,x,ans;
int main()
{
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&x),q.push(-x);
    while(q.size()>1){
        a=q.top();q.pop();
        b=q.top();q.pop();
        ans+=a+b;
        q.push(a+b); 
    }
return !printf("%d",-ans);
}
法四:

堆。其实和上一种做法差不多,弄个小根堆就可以了。

堆的话不想自己写就用 stl的heap吧(不会的点这里http://blog.csdn.net/morewindows/article/details/6967409)

法五(其实的法二是改进版):

贪心:
每次取出两个最小的合并 后再变为一个再插入递增序列里
由于一直sort会超时
所以直接尝试了一下插入排序

#include 
#include 
#include 
int n;
int Ft[10000];
int Fte;    //当前序列里数的个数
int Out;
void Input()  //输入及初始化
{
    scanf("%d",&n);
    int wi;
    for(wi=0;wi
法六(借鉴的一位神犇的):

快排+单调队列
为了快速&简洁,所以快排直接调用std::sort。假设数据用数组a存储,再新建数组b。
分别用两个指针指向两个队列的队首和队尾(p,q指向a;r,s指向b)。由于a,b都是单调队列,所以最少的两堆果子有3种情况:1.a[p]和a[p+1];2.a[p]和b[r];3.b[r]和b[r+1]
合并它们,并插到b的队尾,循环做n-1次即可。
时间复杂度:快排O(nlogn),单调队列O(n),加起来还是[color=red]O(nlogn)[/color]
空间复杂度:需要O(n)的辅助空间
代码复杂度:较低,只有0.6K
实测0ms秒过,瞬间rank1

#include
#include
int a[10010],b[10010],n,t,p,q,r,s,k;
long long ans;
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    ans=0;
    std::sort(a+1,a+n+1);
    p=1;
    q=n;
    r=1;
    s=0;
    for (int i=1;ip){
            t=a[p]+a[p+1];
            k=1;
        }
        if (q>=p && s>=r && a[p]+b[r]r && b[r]+b[r+1]
法七(同样也是借鉴一位神犇的):

每次先集合懒散值最小的两个群,所耗费的体力最小,一般通过快排+二分排序即可解决,但此方法时间复杂度高,因此可以考虑使用计数排序法,其基本思想是设置若干个箱子,依次扫描待排序的数,将关键字=K的记录全装入第K个箱子(分配),然后按序号依次将各非空箱子首尾连接起来(收集)。


先定义一个下标从1~20000的整型数组number[],数组相应下标元素存放对应值的数的个数。


合并时,如合并的值X不超过20000,则number[X]++,即个数加1,如果超过20000,则顺序存入BigNumber[]中,位置由BigNumber[0]决定。然后采取贪心法,依次找最小的两个值,而且两数组已依次排好大小,所以直接顺序取值即可,无需排序。


#include 
#include 
#include 
#include 
using namespace std;
int number[20000+1];//存放下标对应值的数的个数 
int BigNumber[15000+1];//顺序存放大于2万的群数据 
int n;//为人数 
int power;//消耗体力值 
void solve()
{
  int i,p,q;
  long total=0;
  p=1;//存放number数组没合并前的最小指针数 
  q=1;//存放BigNumber数组没合并前的最小指针数 
  i=0;//存放每次合并时已合并的群数 
  while(n>1)//如还没合并完,则继续合并 
  {
    if(p<=20000)//如有个数小于2万的群没有合并 
    {
      if(number[p]>0)//如果number下标对应群存在,则合并 
      {
        i++;
        total+=p;
        number[p]--;
      }
      else
        p++;//没有个数为p的值则往下找 
    }
    else//所有群的个数都大于2万,则顺序从number数据中取出群合并 
    {
      i++;
      total+=BigNumber[q];
      q++;
    }
    if((total<=20000)&&(i==2))//一次合并后(i=2),且合并后的个数不超过2万 
    {
      number[total]++;//将合并的值个数放入小数组中 
      n--;
      i=0;
      power+=total;
      total=0;
    }
    else
      if(i==2)//一次合并完,且合并后的个数大于2万 
      {
        BigNumber[0]++;//哨兵,表示下一次取值从大数组的何位置取 
        BigNumber[BigNumber[0]]=total;//存入大数组 
        n--;
        power+=total;
        i=0;
        total=0;
      }
  }
  printf("%d\n",power);
}
void init()
{
  int i,k;
  scanf("%d",&n);
  for(i=1;i<=n;i++)
  {
    scanf("%d",&k);
    number[k]++;
  }
}
int main()
{
  power=0;
  init();//处理输入数据 
  solve();
  return 0;
}


法八(所见最快的):

先排序a数组,然后在开一个数组b

于是乎就有3个指针了(a数组指针"i",b数组指针"j"和b数组大小的指针"m"

把合并好了的放到b数组的最后(显然b是单调的)

每次取MIN=min{a[i]+a[i+1],b[j]+b[j+1],a[i]+b[j]}

然后ans+=MIN,然后把这个MIN插入到b的末尾,然后指针往后移;

#include
#include
#define aa(x) a[x]+a[x+1]
#define ab(x,y) a[x]+b[y]
#define bb(x) b[x]+b[x+1]
const int N=12017,max=1<<29;
int n,m,i,j,ans,Min,a[N],b[N];
inline int min(int x,int y){return x
我觉得我这个想法还是挺不错的


总结

其实这个题目本身很水,但他却有这么多做法(耗时是递减的)。

其实对于一道题,我们不是会一种做法就ok了,而是应在时间允许的范围内,去钻研最优解,钻研有没有更好的做法,而不是知道一种就够了。

此余之所得矣。

你可能感兴趣的:(模拟)