我发现自己有一个贱毛病,学习之前喜欢先抒情或者表达一些什么特别的感情,之后再开始学习,玩的时候倒是很快能够进入状态,比如说今天的感慨就是:
不管学习什么都要对自己有耐心,学习一点是一点,不浮不躁。好了,我说完了,开始进入今天的正式内容。
重编码问题
重编码
问题描述
有一篇文章,文章包含 n 种单词,单词的编号从 1 至 n,第 i 种单词的出现次数为 w[i]。
现在,我们要用一个 2 进制串(即只包含 0 或 1 的串) s[i] 来替换第 i 种单词,使其满足如下要求:对于任意的 1≤i,j≤n(i≤j),都有 s[i] 不是 s[j] 的前缀。(这个要求是为了避免二义性)
你的任务是对每个单词选择合适的 s[i],使得替换后的文章总长度(定义为所有单词出现次数与替换它的二进制串的长度乘积的总和)最小。求这个最小长度。
字符串 S1(不妨假设长度为 n)被称为字符串 S2 的前缀,当且仅当:S2 的长度不小于 n,且 S1 与 S2 前 n 个字符组组成的字符串完全相同。
输入格式
第一行一个整数 n,表示单词种数。
第 2 行到第 n+1 行,第 i+1 行包含一个正整数 w[i],表示第 i 种单词的出现次数。
输出格式
输出一行一个整数,表示整篇文章重编码后的最短长度。
样例输入
4
1
1
2
2
样例输出
12
思路一:暴力方法,构建哈夫曼树,逐个节点构建(程序貌似没法正确运行,不过我先贴出来了,作为一个思路)
///完整版本的程序
#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
using namespace std;
long long getAnswer(int n,vector <long long> w);
int main(){
int n;
scanf("%d",&n);
//之所以使用向量,是因为向量不用限制长度,如果用数组表示,必须首先指出使用了多少的空间
vector<long long> w;
for(int i=0;i<n;++i){
long long x;
scanf("%lld",&x);
w.push_back(x);
}
sort(w.begin(),w.end());
//print("%lld\n",getAnswer(n,w));
cout<<getAnswer(n,w)<<endl;
return 0;
}
//返回的是一个数值
long long getAnswer(int n,vector <long long> w){
long long sum,count=0;
for (auto i=w.begin();i!=w.end()-1;){
auto k=i+1;//表示当前迭代器的下一个迭代器
sum=*i+*k;//表示两个最小元素的加和
count+=sum;
int flag=0;
cout<<*i<<*k<<sum<<endl;
w.erase(i);
w.erase(i);//参数是迭代器
if(w.size()==0)
return count;
for (auto j=w.begin();j!=w.end();j++){
if(sum<*j){
w.insert(j,sum);
flag=1;
}
} ///
//如果大于所有的,就补充在最后
if(flag==0)
w.insert(w.end(),sum);//在最后插入元素
}
}
//insert 表示在指定位置(loc)表示迭代器,之前插入元素
思路二:采用栈和队列两种数据结构
#include<bits/stdc++.h>
#include<iostream>
using namespace std;
int main(){
vector<int> v;
int n;
int tmp;//表示输入的临时变量
cout<<"请输入元素的个数:";
cin>>n;
for(int i=0;i<n;i++){
cin>>tmp;
v.push_back(tmp);
}
//对向量进行排序
sort(v.begin(),v.end());
reverse(v.begin(),v.end());
stack<int> s;
queue<int> q;//初始化两个数据结构
//将已经有顺序的向量入栈,逆着顺序,在栈里面从上到下是逐渐增加
for(vector<int>::iterator it =v.begin();it!=v.end();it++){
s.push(*it);
}
int sum=0,count=0;//表示最小的两个数的加和
int max=1000000;//表示一个很大的数值
int s_a,s_b,q_a,q_b;
if(s.size()==0)
cout<<"一派胡言!";
if(s.size()==1)
count=s.top();
else{
while(q.size()==0||!(q.size()==1&&s.size()==0)){//最小的尺寸是等于1或者最初的时候
if(s.size()>0){
s_a=s.top();
}
else
s_a=max;
s_b=max;
///同理对于队列而言
if(q.size()>0){
q_a=q.front();
}
else
q_a=max;
q_b=max;
//找出四个数之中最小的两个数值
if(s_a<q_a){
s.pop();
if(!s.empty())
s_b=s.top();
else
s_b=max;
if(s_b<q_a){
sum=s_a+s_b;
s.pop();
}
else{
sum=s_a+q_a;
q.pop();
}
}
else{
q.pop();
if(!q.empty())
q_b=q.front();
else
q_b=max;
if(s_a<q_b){
sum=q_a+s_a;
s.pop();
}
else{
sum=q_a+q_b;
q.pop();
}
}
count+=sum;
q.push(sum);
}
}
cout<<"count:"<<count<<endl;
//一定要仔细对待边界条件
}
思路三:采用优先级队列(priority queue)
//使用优先级队列重编码
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;//表示具体的数目
long long tmp;//用来临时存储输入的数据
priority_queue<int,vector<int>,greater<int> > pq;//定义一个优先队列
vector<long long> v;
cin>>n;
for(int i=0;i<n;i++){
cin>>tmp;
v.push_back(tmp);
}
sort(v.begin(),v.end());
for(vector<long long>::iterator it=v.begin();it!=v.end();it++){
pq.push(*it);//使用一个叫做pq的树状结构表示向量
}
/*
while(!pq.empty()){
cout<<pq.top()<<endl;
pq.pop();
}
*/
long long count=0;
long long a,b,sum=0;
//经过测试,现在数据已经完全被写入树中哈哈哈,优先队列只能从顶端访问数值(我猜的)
if(pq.size()==0)
cout<<"开玩笑~";
if(pq.size()==1)
count=pq.top();//如果只有一个元素,最上面的元素就是我们需要的
while(pq.size()>1){
a=pq.top();
pq.pop();
b=pq.top();
pq.pop();
sum=a+b;
pq.push(sum);
count+=sum;
}
cout<<"count:"<<count<<endl;
return 0;
}