1. 程式人生 > >P1090合併果子

P1090合併果子

題目傳送https://www.luogu.org/problem/show?pid=1090

這道水題有很多種做法是顯然的,,,,在這裡的就介紹一下我的幾種做法吧

法一:

先排序,取兩個最小的相加加入陣列,再排序。。。

(這種做法誰都想得到吧,,,然後顯然超時)

法二:

事先不是已經排過一遍序了嗎?

那我把合併好了的那個數插入到陣列中合適的位置不就ok了嗎?

程式碼:

#include<bits/stdc++.h>
#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(t<n&&a[t]>a[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<int,vector<int>,greater<int> >q;了,直接插入負值就可以(priority_queue<int>q;這樣寫是預設是降序的),然後最後再取ans的相反數就ok了

#include<queue>
#include<cstdio>
using namespace std;
priority_queue<int>q;
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 <stdio.h>
#include <algorithm>
#include <memory.h>
int n;
int Ft[10000];
int Fte;    //當前序列裡數的個數
int Out;
void Input()  //輸入及初始化
{
    scanf("%d",&n);
    int wi;
    for(wi=0;wi<n;++wi)
        scanf("%d",Ft+wi);
    std::sort(Ft,Ft+n);
    Fte=n;
}
void Stuck(int na)  //把一個數插入有序陣列中,方便起見沒用二分查詢或者lower_bound
{
    Fte-=2;    
    if(Fte==0)
    {
        Ft[0]=na;
        ++Fte;
        return;
    }
    memmove(Ft,Ft+2,(Fte)*4);
    if(Ft[Fte-1]<=na)
    {
        Ft[Fte]=na;
        ++Fte;
        return;
    }
    int wi=0;
    while(Ft[wi]<na)++wi;
    memmove(Ft+wi+1,Ft+wi,(Fte-wi)*4);
    Ft[wi]=na;
    ++Fte;
    return;
}
int main()
{
    Input();
    int bf;
    while(1)
    {
        if(Fte==1)     //邊界條件——序列裡只有一個數(只有一堆某東西)退出
            break;
        bf=Ft[0];        //取出前兩個最小的
        bf+=Ft[1];
        Out+=bf;
        Stuck(bf);
    }
    printf("%d",Out);
    return 0;
}
法六(借鑑的一位神犇的):

快排+單調佇列
為了快速&簡潔,所以快排直接呼叫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<cstdio>
#include<algorithm>
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;i<n;i++){
        t=2100000000;
        if (q>p){
            t=a[p]+a[p+1];
            k=1;
        }
        if (q>=p && s>=r && a[p]+b[r]<t){
            t=a[p]+b[r];
            k=2;
        }
        if (s>r && b[r]+b[r+1]<t){
            t=b[r]+b[r+1];
            k=3;
        }
        ans=(long long)t+ans;
        if (k==1) p+=2;
        if (k==2){
            p++;
            r++;
        }
        if (k==3) r+=2;
        s++;
        b[s]=t;
    }    
    printf("%lld\n",ans);
}
法七(同樣也是借鑑一位神犇的):

每次先集合懶散值最小的兩個群,所耗費的體力最小,一般通過快排+二分排序即可解決,但此方法時間複雜度高,因此可以考慮使用計數排序法,其基本思想是設定若干個箱子,依次掃描待排序的數,將關鍵字=K的記錄全裝入第K個箱子(分配),然後按序號依次將各非空箱子首尾連線起來(收集)。


先定義一個下標從1~20000的整型陣列number[],陣列相應下標元素存放對應值的數的個數。


合併時,如合併的值X不超過20000,則number[X]++,即個數加1,如果超過20000,則順序存入BigNumber[]中,位置由BigNumber[0]決定。然後採取貪心法,依次找最小的兩個值,而且兩陣列已依次排好大小,所以直接順序取值即可,無需排序。


#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
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<cstdio>
#include<algorithm>
#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<y?x:y;}
int main()
{
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%d",a+i),b[i]=max;
	if(n==1)return !puts("0");
	std::sort(a+1,a+n+1);a[n+1]=max;
    b[1]=a[1]+a[2];ans=b[1];
	i=3;j=1;m=1;
	while(i<=n||j<m)
	{
		Min=min(ab(i,j),min(aa(i),bb(j)));
		if(Min==ab(i,j))
		    b[++m]=a[i]+b[j],ans+=b[m],i++,j++;
		else if(Min==aa(i))
		    b[++m]=a[i]+a[i+1],ans+=b[m],i+=2;
		else
		    b[++m]=b[j]+b[j+1],ans+=b[m],j+=2;
	}
return !printf("%d",ans);
}
我覺得我這個想法還是挺不錯的

總結

其實這個題目本身很水,但他卻有這麼多做法(耗時是遞減的)。

其實對於一道題,我們不是會一種做法就ok了,而是應在時間允許的範圍內,去鑽研最優解,鑽研有沒有更好的做法,而不是知道一種就夠了。

此餘之所得矣。