解题报告:luogu P2558
题目链接:P2558 [AHOI2002]网络传输
\(dp\) 入门题,结果调了一晚上,枯了。
很显然 \(x^{n+1}>\sum\limits_{i=0}^n x^n\),那么我们对于每个次方数,加上他前面的任意数的组合一定小于下一个次方数。
容易得到:
\[f_{i}=f_{2^{\left\lfloor\log_{2}i\right\rfloor}}+f_{i-2^{\left\lfloor\log_{2}i\right\rfloor}}
\]
可以递推实现。
然而我们发现最后的结果小于 \(2^{50}\) ,高精好麻烦啊!
鉴于 \(50\) 比较小,我们尝试压位。
把一个数定义成结构体形式:
struct num
{
ll a,b,c,d;
};
考虑到 \(\left\lceil\dfrac{50}{4}\right\rceil=13\) 在long long
范围完全可以接受,于是把一个数分成四块。
然后定义运算就好了:
加法:
num add(num x,num y)
{
num z;
z.a=z.b=z.c=0;
z.a=y.a+x.a;
z.b=y.b+x.b;
z.c=y.c+x.c;
z.d=y.d+x.d;
return arrg(z);
}
这里的arrg
函数是把不符合一块内最多 \(13\) 位的数整理成符合条件的数。
可以这样写:
#define MOD 10000000000000
num arrg(num x)
{
num z=x;
if(z.a>=MOD) z.b+=z.a/MOD,z.a%=MOD;
if(z.b>=MOD) z.c+=z.b/MOD,z.b%=MOD;
if(z.c>=MOD) z.d+=z.c/MOD,z.c%=MOD;
return z;
}
然后还有普通数乘分块数:
num mul(num x,ll k)
{
num z;
z.a=z.b=z.c=0;
z.a=x.a*k;
z.b=x.b*k;
z.c=x.c*k;
z.d=x.d*k;
return arrg(z);
}
这样,我们就减少了原要写高精的码量(bushi,输出打的我焦头烂额。
时间复杂度为 \(\mathcal O(p)\),可以通过本题。
完整代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
#define ll long long
#define MOD 10000000000000
struct num
{
ll a,b,c,d;
//压13位
}ans[1030],mi[20];
int n,p,cnt=0;
num arrg(num x)
{
num z=x;
if(z.a>=MOD) z.b+=z.a/MOD,z.a%=MOD;
if(z.b>=MOD) z.c+=z.b/MOD,z.b%=MOD;
if(z.c>=MOD) z.d+=z.c/MOD,z.c%=MOD;
return z;
}
num add(num x,num y)
{
num z;
z.a=z.b=z.c=0;
z.a=y.a+x.a;
z.b=y.b+x.b;
z.c=y.c+x.c;
z.d=y.d+x.d;
return arrg(z);
}
num mul(num x,ll k)
{
num z;
z.a=z.b=z.c=0;
z.a=x.a*k;
z.b=x.b*k;
z.c=x.c*k;
z.d=x.d*k;
return arrg(z);
}
int logx(int a,int b){return ceil(log(b)/log(a));}
ll _pow(ll a,ll b){ll ans=1;for(int i=1;i<=b;i++) ans*=a;return ans;}
int main()
{
scanf("%d%d",&n,&p);
mi[0].a=1ll,mi[0].b=0,mi[0].c=0,mi[0].d=0;
ans[++cnt]=mi[0];
for(int i=1;i<=logx(2,p)+1;i++) mi[i]=mul(mi[i-1],(ll)n);
if(p==1){printf("1\n");return 0;}
int k=1;
for(;;)
{
ans[++cnt]=mi[k++];
if(cnt>=p) break;
int now=cnt-1;
for(int j=1;j<=now&&cnt<=p;j++) ans[++cnt]=add(ans[now+1],ans[j]);
if(cnt>=p) break;
}
ans[p]=arrg(ans[p]);
if(ans[p].d!=0)
{
printf("%lld",ans[p].d);
for(int i=12;i>=0;i--)
{
ll g=_pow((ll)10,i);
printf("%d",(int)(ans[p].c/g));
ans[p].c%=g;
}
for(int i=12;i>=0;i--)
{
ll g=_pow((ll)10,i);
printf("%d",(int)(ans[p].b/g));
ans[p].b%=g;
}
for(int i=12;i>=0;i--)
{
ll g=_pow((ll)10,i);
printf("%d",(int)(ans[p].a/g));
ans[cnt].a%=g;
}
}
else if(ans[p].c!=0)
{
printf("%lld",ans[p].c);
for(int i=12;i>=0;i--)
{
ll g=_pow((ll)10,i);
printf("%d",(int)(ans[p].b/g));
ans[p].b%=g;
}
for(int i=12;i>=0;i--)
{
ll g=_pow((ll)10,i);
printf("%d",(int)(ans[p].a/g));
ans[p].a%=g;
}
}
else if(ans[p].b!=0)
{
printf("%lld",ans[p].b);
for(int i=12;i>=0;i--)
{
ll g=_pow((ll)10,i);
printf("%d",(int)(ans[p].a/g));
ans[p].a%=g;
}
}
else printf("%lld",ans[p].a);
putchar('\n');
return 0;
}
开始我尝试把一个数分成 \(3\) 块,然而爆long long
了,只好分成 \(4\) 块。