P3885方程的解(较详细)
Posted snowysniper
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3885方程的解(较详细)相关的知识,希望对你有一定的参考价值。
题目链接:https://www.luogu.org/problem/P1771
想要更多磨练的,这里https://gmoj.net/senior/#main/show/1344
区别在内存空间的限制上。
首先,观察这道题。
- 这是一道数学题。(废话
- x给的范围很大,考虑用到快速幂,然后要求分解的那个数(以下统称n)最大就是1000。
- 去掉x大小的限制之后,发现题目剩余的部分是一种模板:给出n,要求分为k份,求分法。就是标准的组合数。
- 再次考虑数据范围,分法大概是:[6*S*k/(x^3)]+(6*S)/(x^2)种,其中S=2^(n-1),x=n+1.(似乎哪里有问题但总之这个很大就对了),发现要用高精度
观察结束。
实现:组合数学(递推),快速幂,高精加。
公式:C(k,x^x%1000)
递推式应该比较容易想到:假设目前要分解 n ,把它分解成 m 份。现在先把 n 分成 1 和 n-1,答案加上:(分解 n-1 时的分法数)*(分解1时的分法数)。然后把 n 分成 2 和 n-2,答案加上(分解 n-2 时的分法数)* (分解2时的分法数)以此类推。注意这里分解成n-1和1与分解成n-2和2是两种情况,都要加上。其实应该就是隔板法的思想。
到了实现的步骤。首先要用快速幂处理 x 得到要分解的数 n ,然后递推:把1分成1份,把2分成1、2份,把3分成1、2、3份分别是多少种分法,用高精存起来。具体用dt[i][j]表示把i分成j份能有几种分法。顺带一提,杨辉三角拿邻接矩阵存起来,第i行第j个数就是把i分解成j份的分法,因为杨辉三角也有递推的性质。
这里放两个版本的代码,第一个方便理解一些;第二个加上了高精,还算了一下:要是用邻接矩阵会有8万多kb,因为纪中OJ这道题的空间限制 65536KB (不像洛谷给了125兆),所以加上了滚动数组。(当然65536KB的空间只能卡掉邻接矩阵,闲的没事用邻接表、压位高精之类也是可以的w)
附代码:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #define ll long long using namespace std; ll x; int n,k,maxn; ll dt[1100][110];//把i分成j组的情况数 (杨辉三角 int ksm(int a,ll b,int mo) int ret=1; while(b) if(b&1)ret=ret*a%mo; a=a*a%mo; b>>=1; return ret; int main() cin>>k>>x; int n=x%1000;//求n的x次方%1000 n=ksm(n,x,1000);//求n分成k组的组合数 (n<=1000) for(int i=1;i<=n;i++) dt[i][1]=1; if(i<=k) dt[i][i]=1; for(int i=3;i<=n;i++)//画个杨辉三角就知道为什么从3开始。。 for(int j=2;j<=i&&j<=k;j++)//将i分成j组 dt[i][j]=dt[i-1][j-1]+dt[i-1][j]; cout<<dt[n][k];
和:
#include<iostream> #include<algorithm> #include<cstring> #define Lovelive long long using namespace std; Lovelive x; int n,k,maxn; char dt[2][101][400]; int al[400],bl[400],cl[400]; int first; int ksm(int a,Lovelive b,int mo) int ret=1; while(b) if(b&1)ret=ret*a%mo; a=a*a%mo; b>>=1; return ret; int main() cin>>k>>x; int xx=x%1000;//求xx的x次方%1000 n=ksm(xx,x,1000);//求n分成k组的组合数 (n<=1000) dt[0][1][0]=‘1‘; dt[0][2][0]=‘1‘; for(int i=3;i<=n;i++) for(int j=2;j<i&&j<=k;j++)//将i分成j组 dt[1-first][1][0]=‘1‘;//以下是高精操作 ,大意是把两个大数加起来(废话 memset(al,0,sizeof(al)); memset(bl,0,sizeof(bl)); memset(cl,0,sizeof(cl)); //dt[i-1][j-1],dt[i-1][j]; int lena=strlen(dt[first][j-1]); int lenb=strlen(dt[first][j]); for(int l=1;l<=lena;l++) al[l]=dt[first][j-1][lena-l]-‘0‘; for(int l=1;l<=lenb;l++) bl[l]=dt[first][j][lenb-l]-‘0‘; int lenc=1,yu=0; while(lenc<=lena||lenc<=lenb) cl[lenc]=al[lenc]+bl[lenc]+yu; yu=cl[lenc]/10; cl[lenc]%=10; lenc++; cl[lenc]+=yu; if(cl[lenc]==0)lenc--; int flag=0; while(lenc) dt[1-first][j][flag]=cl[lenc]+‘0‘; lenc--; flag++; first=1-first; if(i<=k) dt[first][i][0]=‘1‘; cout<<dt[first][k];
补充纪中OJ上的得分情况:20分是乱搞到的,40分是有策略无高精,60与AC的差距在有没有优化空间
以上是关于P3885方程的解(较详细)的主要内容,如果未能解决你的问题,请参考以下文章