蚂蚁搬沙(2017佛山市选拔初中组)
题目描述
山谷中住着一个巨大的蚂蚁王国,蚁穴外有一个整洁的广场,天气晴好时蚁群常在那里举行各种活动。这天夜里,天降沙尘,第2天,广场上堆满了大大小小的沙堆,蚁哨出去数了数共有n堆,蚁后要求她的臣民将广场上的沙堆清理掉。具体办法是:每次可以把广场上的任意k堆沙子合并成一堆,重复进行直至所有的沙堆最终合并成一堆。规定(1):2≤k≤m,m由蚁后指定,(2):每次合并k堆沙子的代价是这k堆沙子的重量和。
你的任务是,对给定的n和m,计算出将n堆沙子最终合并成1堆的最小总代价。
例如,广场上有7堆沙子,其重量分别为45,13,12,16,9,5,22。当m=3时,这些沙堆合并成一堆的最小总代价为199。当m=5时,这些沙堆合并成一堆的最小总代价为148。
输入格式 1757.in
第一行2个正整数,分别表示n(n≤100000)堆沙子和每次合并时可以合并的最大堆数m,从第二行开始有n个数,表示n堆沙子的重量(1~500),数与数之间用空格隔开。
输出格式 1757.out
只包含一个正整数,表示将n堆沙子合并成1堆所需的最小总代价。
输入样例 1757.in
7 3
45 13 12 16 9 5 22
输出样例 1757.out
199
一开始做这题的时候,我连样例都看了半天。首先,由5+9+12=26,再由13+16+22=51,最后26+45+51=122,最终答案为26+51+122=199。
所以我们会发现,在前面加的,会被重复累加更多次,所以每一次优先堆的最好越小越好,而且越多。
这题用到优先队列,我写代码的时候犯了一个小小的错误,把普通队列的定义误以为是优先队列priority_queue<int> s;(一开始定义了queue<int> s),对优先队列的使用还不太熟悉。
如果当(n-1)%(m-1)没有余数时,则从小的取起,每一堆都放m个,这样求出来的就是最大值。
但是如果有多余的呢?如果前面一堆都有m个,那最后的累加部分就会变多,不是最优的。所以,我们要让重的尽量添加少的次数。算出n%(m-1),把多余的部分垒成一堆,把特殊的情况变回原来熟悉的(n-1)%(m-1)== 0,这样子又可以重复回上面的操作了。
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdio>
using namespace std;
int n,m,a,ans;
priority_queue<int> s;
int main()
{
freopen("1757.in","r",stdin);
freopen("1757.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a;
s.push(-a);
}
if((n-1)%(m-1)==0)
{
int k=(n-1)/(m-1);
while(k)
{
int now=0;
for(int i=1;i<=m;i++)
{
now=now+s.top();
s.pop();
}
ans+=now;
s.push(now);
k--;
}
}
else
{
int k=n%(m-1),now=0;
for(int i=1;i<=k;i++)
{
now=now+s.top();
s.pop();
}
ans+=now;
s.push(now);
int v=(n-k)/(m-1);
while(v)
{
int now=0;
for(int i=1;i<=m;i++)
{
now=now+s.top();
s.pop();
}
ans+=now;
s.push(now);
v--;
}
}
cout<<-ans<<endl;
return 0;
}