题解 P1974 【基因聚合】
JohnJoeZhu · · 题解
题目传送门
算法标签:贪心 (水) ,高精,队列(不是很多)
PART 1 :题目解析
如果你认真读题,其实会发现 (废话一堆) 就一句话:
从n个1中,取出任意两个数相乘再加一,在序列删除两数并加入新的结果,问最后剩下的数的最大值。
PART 2 :贪心
为什么这题要用贪心呢?(前提是你认真读题)
如果要求结果最大,那么我们期望每一步得到的结果都最大,则必须两个因数最大
既然要因数最大,那么我们就拿尽可能小的数进行操作,使得在底层获得的1更多,得到的结果不断叠加,则能够使结果最大
PART 3 :高精
如果你仔细想想 (别问我怎么想的),答案会十分巨大
你不用高精,怎么办???
在处理高精部分时,我习惯用重载运算符的方法,具体实现见底部代码哦
(吐槽一下烦人的高精)
blog:重载运算符
大佬讲解:重载运算符
PART 4 :队列
在贪心的基础上,我们很容易想到使用小根堆来实现
但是,堆/优先队列+高精 太 TM 恶心了,而且如果你非要用的话,不仅加大了码量,而且会MLE
怎么办呢?
我们发现,答案具有单调性,简单来说,就是每次操作都会得到第一大的数(你也可以认为这是贪心的理由)
所以,我们只需要用队列不就得了,保证了队列的前两个元素一定是最小的
但是,我们还会MLE
因为我们把高精的数组,一起存在了队列中,而且同时会占用O(n*(n+1)/2)的高精数组空间,那不就MLE了
既然如此,我们为什么不可以利用已经不要的数组空间呢
在此,我们就用队列,存储数组下标,毕竟我们不需要关心每个数的大小了
PART 5 :代码
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
struct pp{
int l,g[3000];//l存长度,g存数字
pp()//这里一定要初始化
{
l=0;
memset(g,0,sizeof(g));
}
void print()
{
for(int i=l-1;i>=0;i--)//输出
printf("%d",g[i]);
printf("\n");
}
}f[10010];
int n;
queue<int>q;//这里就存数组下标了
pp operator +(const pp &a,const pp &b)//重载运算符
{
pp c;
c.l=max(a.l,b.l);
int x=0;
for(int i=0;i<c.l;i++)//普通高精打法(贴板子)
{
c.g[i]=a.g[i]+b.g[i]+x;
x=c.g[i]/10;
c.g[i]%=10;
}
if(x>0) c.g[++c.l]=x;//进位
return c;
}
pp operator *(const pp &a,const pp &b)//还是重载运算符
{
pp c;
for(int i=0;i<a.l;i++)
for(int j=0;j<b.l;j++)
c.g[i+j]+=a.g[i]*b.g[j];
c.l=a.l+b.l-1;
for(int i=0;i<c.l;i++)
{
c.g[i+1]+=c.g[i]/10;
c.g[i]=c.g[i]%10;
}
while(c.g[c.l++]);//注意这里的高精计算
while(!c.g[c.l-1]) c.l--;//同上
return c;
}
int main()
{
scanf("%d",&n);
pp k;
int ans,anss;
k.g[0]=1;
k.l++;
for(int i=1;i<=n;i++) q.push(i),f[i]=k;
while(q.size()!=1)//留下最后的答案
{
ans=q.front();
q.pop();
anss=q.front();
q.pop();
f[ans]=f[ans]*f[anss];
f[ans]=f[ans]+k;
q.push(ans);
}
ans=q.front();
f[ans].print();
return 0;
}