P2549 计算器写作文 题解
Post time: 2020-12-01 19:37:35
我们首先考虑把两个单词合起来的运算定义为 \(+\),那么如果 \(A+B>B+A\),无论怎么合并,我们都一定会把 \(A\) 放在 \(B\) 的前面。
于是我们可以通过这样一个判定方法将所有的单词排序,然后跑背包。即设 \(f_{i,j}\) 表示前 \(i\) 个单词,长度为 \(j\) 时能够得到的最大值。那么有:
\[f_{i,j}=\max(f_{i-1,j},f_{i-1,j-a_i.len}+a_i )
\]
注意这里的 \(+\) 就是上面我们定义的 \(+\),与此同时,\(\max\) 运算也需要特殊比较,方法是这样:
bool operator >(const Big &A)const{
if(len!=A.len) return len>A.len;
for(int i=1;i<=len;++i)
if(d[i]!=A.d[i]) return d[i]>A.d[i];
return 0;
}
就比较像高精度里边的比较大小。
但是这样的话并不能通过所有的数据,为什么呢?
我们发现,这个做法在个位为 \(0\) 时不成立,因为小数的大小比较不比较长度,只比较每一位。也就是说我们需要判断,是不是所有的单词都以 \(0\) 开头。这时候我们前面的排序也需要按照这种新方式,然后同样跑一个背包。\(\max\) 运算的比较方法变成了:
bool operator <(const Big &A)const{
int tmp=min(len,A.len);
for(int i=1;i<=tmp;++i)
if(d[i]!=A.d[i]) return d[i]<A.d[i];
return len<A.len;
}
这样我们就可以通过这道题。
这个题的单词,我是通过 struct
定义了大整数类型来存储的。这样做有很大的优势,因为可以通过 operator
重载运算符,使代码非常简洁。建议大家尝试。
总结一下思考这类题的方式:首先把 dp 方程设出来,发现是一个背包,背包 dp 需要考虑顺序问题,它无法处理顺序可能不同的选择。所以我们根据两个单词合并的大小得到了顺序,可以跑背包了。然后我们发现 \(0\) 开头所组成的小数,因为判定大小的方法不同而需要重新处理。最终我们综合以上想法可以通过此题。
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
using namespace std;
const int N=200+13,M=10000+13;
struct Big{//使用了struct自定义类型存储,优势明显
int d[N],len;
Big(){memset(d,0,sizeof d);len=0;}//初始化
Big operator +(const Big &A)const{
Big Ans;
for(int i=1;i<=len;++i) Ans.d[i]=d[i];
for(int i=1;i<=A.len;++i) Ans.d[i+len]=A.d[i];
Ans.len=len+A.len;
return Ans;
}
bool operator >(const Big &A)const{//第一种比较方式
if(len!=A.len) return len>A.len;
for(int i=1;i<=len;++i)
if(d[i]!=A.d[i]) return d[i]>A.d[i];
return 0;
}
bool operator <(const Big &A)const{//第二种,整数位为0时的比较方式
int tmp=min(len,A.len);
for(int i=1;i<=tmp;++i)
if(d[i]!=A.d[i]) return d[i]<A.d[i];
return len<A.len;
}
}a[M],f[N];
ostream &operator <<(ostream &output,Big A){//并不非常常见的重载输出
if(A.d[1]==0){
output<<"0.";
for(int i=2;i<=A.len;++i) output<<A.d[i];
}
else for(int i=1;i<=A.len;++i) output<<A.d[i];
output<<endl;return output;
}
int n,m;
map<char,int> ms;
inline void init(){
ms['O']=ms['D']=0,ms['G']=9,ms['B']=8,ms['L']=7,
ms['q']=6,ms['S']=5,ms['h']=4,ms['E']=3,ms['Z']=2,ms['I']=1;
}
inline bool cmp1(Big A,Big B){return (A+B)>(B+A);}
inline bool cmp2(Big A,Big B){
int tmp=min(A.len,B.len);
for(int i=1;i<=tmp;++i)
if(A.d[i]!=B.d[i]) return A.d[i]>B.d[i];
return A.len>B.len;
}
inline void solve1(){
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=n;++i)
for(int j=m;j>=a[i].len;--j){
Big Tmp=f[j-a[i].len]+a[i];
if(Tmp>f[j]) f[j]=Tmp;
}
cout<<f[m];
}
inline void solve2(){
sort(a+1,a+n+1,cmp2);
for(int i=1;i<=n;++i){
for(int j=m;j>=a[i].len;--j){
Big Tmp=f[j-a[i].len]+a[i];
if(f[j]<Tmp) f[j]=Tmp;
}
}
cout<<f[m];
}
int main(){
init();//预处理字母与数字的对应关系,用map比较容易实现
scanf("%d%d",&m,&n);
for(int i=1;i<=n;++i){
char s[40];
scanf("%s",s+1);
int l=strlen(s+1);
a[i].len=l;
for(int j=l;j>=1;--j) a[i].d[l-j+1]=ms[s[j]];
}
bool state=0;
for(int i=1;i<=n;++i)
if(a[i].d[1]>0){state=1;break;}
if(state) solve1();//需要不同的大小判断,所以分两种不同的方式解决
else solve2();
return 0;
}