为简化文章,原题请直接看原题链接
给你一块长木板,其长度为L,要求将其切割成若干块指定大小的木板,大小分别为a,b,c,d…
其中L恰好等于a+b+c+d+…,每切割一次,所需的花费为当前执行切割操作的木板的的长度。比如将长度为P的木板切割为K和H,则花费为P。求最小的花费的切割方法的所需花费。
第1行给出整数 N,N代表切割后的木板的个数
第2到N+1行,每行给出1个整数,代表一块切割后的木板的长度
输出一行,该行含有一个整数K,K代表题意所需的结果。
3
8
5
8
34
示例输入输出的结果34是这样子得来的:21切割成13和8,花费21。将长度为13的木板切割为8和5,花费13.一共花费21+13=34。
以“二叉树”的形式,看待每一次切割。
将执行分割的木板长度为A[ i ],分割后的木板的长度分别为 A[ 2*i ] , A[ 2*i+1 ]
则有A[ i ]=A[ 2*i ]+A[ 2*i+1 ]
经过稍微思考,可知所需花费是非叶子结点的权值的总和·
再经过观察,不难发现 所有“叶子结点的权值乘以其路径”的总和为其所需花费
根据以上特征,不难联想到哈夫曼树。
对哈夫曼树不了解的同学请自行搜索,了解,这里不再赘述。
#include
#include
using namespace std;
typedef long long ll;
//maxn将最大堆变最小堆,请看后续代码
const ll maxn = 1000000009;
//树
struct Node{
ll sum;//构建该树的总耗费
ll basic;//叶子结点的权值总值
};
//构建最大堆之用
bool operator<(const Node &a,const Node &tmp){
return a.basic<tmp.basic;
}
//最大堆
priority_queue<Node> q;
//将叶子结点的权值总值,即树根节点的权值进行类似于“求补码”的操作
inline void re(Node &tmp){
//maxn的用处在此
tmp.basic=maxn-tmp.basic;
}
int main(){
ll n,tmp;
//前两大的树(求补后即前两小)
Node work;
Node work2;
work.sum = 0;
scanf("%lld",&n);
for(ll i = 0;i<n;++i){
scanf("%lld",&tmp);
work.basic = tmp;
//求补,最大变最小
re(work);
q.push(work);
}
while(q.size()!=1){
work = q.top();q.pop();
work2 = q.top();q.pop();
//求补,还原数据
re(work);
re(work2);
//更新叶子结点权值总值
work.basic+=work2.basic;
//更新总耗费
work.sum+=work2.sum;
work.sum+=work.basic;
//求补,重新入优先级队列
re(work);
q.push(work);
}
printf("%lld\n",q.top().sum);
return 0;
}