P1090合併果子
題目傳送https://www.luogu.org/problem/show?pid=1090
這道水題有很多種做法是顯然的,,,,在這裡的就介紹一下我的幾種做法吧
法一:
先排序,取兩個最小的相加加入陣列,再排序。。。
(這種做法誰都想得到吧,,,然後顯然超時)
法二:
事先不是已經排過一遍序了嗎?
那我把合併好了的那個數插入到陣列中合適的位置不就ok了嗎?
程式碼:
這個做法雖然不會超時(對於洛谷的資料,最大的300ms左右),但是效率還是比較低的。#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; }
法三:
想必優先佇列都知道吧(不會的點這裡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了,而是應在時間允許的範圍內,去鑽研最優解,鑽研有沒有更好的做法,而不是知道一種就夠了。
此餘之所得矣。