P3885方程的解(较详细)

Posted snowysniper

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P3885方程的解(较详细)相关的知识,希望对你有一定的参考价值。

题目链接:https://www.luogu.org/problem/P1771

想要更多磨练的,这里https://gmoj.net/senior/#main/show/1344

区别在内存空间的限制上。


 

Description

  佳佳碰到了一个难题,请你来帮忙解决。   对于不定方程a1+a2+……+ak-1+ak=g(x),其中k>=2且k∈N*,x是正整数,g(x)=x^x mod 1000(即xx除以1000的余数),x,k是给定的数。我们要求的是这个不定方程的正整数解组数。   举例来说,当k=3,x=2时,分别为(a1,a2,a3)=(2,1,1),(1,2,1),(1,1,2).
 

Input

  输入文件有且只有一行,为用空格隔开的两个正整数,依次为k,x。

Output

  输出文件有且只有一行,为方程的正整数解组数。
 

Sample Input

3 2

Sample Output

3

首先,观察这道题。

  1. 这是一道数学题。(废话
  2. x给的范围很大,考虑用到快速幂,然后要求分解的那个数(以下统称n)最大就是1000。
  3. 去掉x大小的限制之后,发现题目剩余的部分是一种模板:给出n,要求分为k份,求分法。就是标准的组合数。
  4. 再次考虑数据范围,分法大概是:[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方程的解(较详细)的主要内容,如果未能解决你的问题,请参考以下文章

请问中国剩余定理是啥?请说的详细点

二元一次方程组怎么解

哪有关于Linux系统的安装过程全解

二元回归方程公式详细步骤

拉普拉斯方程数学符号∂是啥意思,请举一个拉普拉斯方程完整例子

增量型算式 小算法