pongo英雄会-幸运数题解

显然我们只要知道1~x范围有多少幸运数(用f(x)表示),lucky(x,y)=f(y)-f(x-1).

解法1. 计算排列数

由于y<=1000000000这个规模,我们不能暴力验证每个数是否是幸运数。可以想到,对于同样的数字组成,不同的数字排列对应不同的幸运数,比如12,21。那么就只需枚举合法的数字组成,算出相应的排列数。设数字i有a[i]个,n=Σa[i],则对应的排列数是n!/∏a[i]!。

接下来就只要枚举那些合法的数字组成了。我们希望枚举时对每位的可取数字是没有限制的,可以分类来进行。下面举例说明。

比如x=2345. 当千位是0或1时,后三位是没有限制的,0~9都可以,可以用递归来枚举a[i],然后结合已确定的千位来判断这种组成是否是幸运数,是的话答案就加上相应的排列数。

当千位等于2时,继续依次固定百位=0,1,2计算。余此类推。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<time.h>
using namespace std;

bool isPrime(int n)
{
    if(n<2)  return false;
    int i;
    for(i=2;i*i<=n;i++) if(n%i==0) return false;
    return true;
}

bool check(int cnt[],int n1,int n2)
{
    int i;
    int s1=n1,s2=n2;
    for(i=1;i<10;i++)
    {
        s1+=i*cnt[i];
        s2+=i*i*cnt[i];
    }
    return isPrime(s1)&&isPrime(s2);
}

int fac[10];
void comFactorial()
{
    int i;
    fac[0]=1;
    for(i=1;i<10;i++) fac[i]=fac[i-1]*i;
}

int nPermutation(int cnt[])
{
    int i;
    int n=0;
    for(i=0;i<10;i++) n+=cnt[i];
    int ans=fac[n];
    for(i=0;i<10;i++) ans/=fac[cnt[i]];
    return ans;
}

void dfs(int digit,int n,int cnt[],int n1,int n2,int &ans)
{
    int i;
    if(digit==10)
    {
        cnt[0]=n;
        if(check(cnt,n1,n2))
        ans+=nPermutation(cnt);
        return ;
    }
    for(i=0;i<=n;i++)
    {
        cnt[digit]=i;
        dfs(digit+1,n-i,cnt,n1,n2,ans);
    }
}

int com(int x)
{
    comFactorial();
    int digit[12];
    int cnt[12];
    int i,j,n=0;
    while(x>0)
    {
        digit[n++]=x%10;
        x/=10;
    }
    int n1=0,n2=0,s1,s2,ans=0;
    for(i=n-1;i>0;i--)
    {
        for(j=0;j<digit[i];j++)
        {
            s1=n1+j;
            s2=n2+j*j;
            dfs(1,i,cnt,s1,s2,ans);
        }
        n1+=digit[i];
        n2+=digit[i]*digit[i];
    }
    for(i=0;i<=digit[0];i++)
    {
        s1=n1+i;
        s2=n2+i*i;
        if(isPrime(s1)&&isPrime(s2)) ans++;
    }
    return ans;
}

int lucky(int x,int y)
{
    return com(y)-com(x-1);
}

  

解法2. dp

dp[n][s1][s2]表示n位,各位数字的和为s1,平方和为s2的方法数。根据第一位的数字来状态转移,dp[n][s1][s2]=Σdp[n-1][s1-i][s2-i*i] i=0...9

两种方法的共同之处是都要逐渐固定高位来分类计算。

#include <cstring>
#include <vector>
#include <iostream>
#include <set>
#include <map>
#include <algorithm>
#include<deque>
#include<cstdio>
#include<time.h>
using namespace std;

int dp[12][82][730];

bool isPrime(int n)
{
    int i;
    if(n<2) return false;
    for(i=2;i*i<=n;i++) if(n%i==0) return false;
    return true;
}


int f(int n,int s1,int s2)
{
    if(dp[n][s1][s2]!=-1) return dp[n][s1][s2];
    if(n==1)
    {
        if(s1<10&&s1*s1==s2) return dp[n][s1][s2]=1;
        else return dp[n][s1][s2]=0;
    }
    else
    {
        int i;
        int ans=0;
        for(i=0;i<10&&i<=s1&&i*i<=s2;i++) ans+=f(n-1,s1-i,s2-i*i);
        return dp[n][s1][s2]=ans;
    }
}

int com(int x)
{
    int a[20];
    int num=0;
    while(x>0)
    {
        a[num++]=x%10;
        x/=10;
    }
    int ans=0;
    int i,j,k,m;
    int s1=0,s2=0;
    int p1=0,p2=0;
    for(k=num-1;k>0;k--)
    {
        for(m=0;m<a[k];m++)
        {
            s1=p1+m;
            s2=p2+m*m;
    for(i=0;i<=9*k;i++)
     for(j=i;j<=81*k;j++)
     {
         if(isPrime(i+s1)&&isPrime(j+s2))
         ans+=f(k,i,j);
     }
        }
        p1+=a[k];
        p2+=a[k]*a[k];
    }
    for(i=0;i<=a[0];i++)
    if(isPrime(p1+i)&&isPrime(i*i+p2)) ans++;
    return ans;
}

int lucky(int x,int y)
{
    memset(dp,-1,sizeof(dp));
    return com(y)-com(x-1);
}

  

转自 http://blog.csdn.net/shuyechengying/article/details/9164517

 
原文地址:https://www.cnblogs.com/downtjs/p/3197899.html