UVA1575 排列组合+dfs减枝

uva 1575 排列组合+dfs减枝

参考了这篇博客, 自己计算证明了为什么用那么些20多个素数就可以。

题意

给出n,对于f(k),求最小的k.

f(k) 的定义是 对k 质因数分解,问这些质因数的排列组合数。

思路

主要思路
  1. 对于k, k = p 1 a 1 ∗ p 2 a 2 . . . ∗ p m a m k=p_1^{a_1}*p_2^{a_2}...*p_m^{a_m} k=p1a1p2a2...pmam,排列组合数就是 ( a 1 + a 2 + . . . a m ) ! a 1 ! ∗ a 2 ! ∗ . . . a m ! \frac{(a_1+a_2+...a_m)!}{a_1!*a_2!*...a_m!} a1!a2!...am!(a1+a2+...am)! ,由此可见,其实与素数是什么没有关系的,主要是各个素数的数量是多少。

  2. x 0 = ( a 1 + a 2 + . . . a m ) ! a 1 ! ∗ a 2 ! ∗ . . . a m ! x_0=\frac{(a_1+a_2+...a_m)!}{a_1!*a_2!*...a_m!} x0=a1!a2!...am!(a1+a2+...am)!

    x 1 = ( a 1 + a 2 + . . . a m + a m + 1 ) ! a 1 ! ∗ a 2 ! ∗ . . . a m ! ∗ a m + 1 ! x_1=\frac{(a_1+a_2+...a_m+a_{m+1})!}{a_1!*a_2!*...a_m!*a_{m+1}!} x1=a1!a2!...am!am+1!(a1+a2+...am+am+1)!

    x 1 x 0 = ( a 1 + a 2 + . . . a m + a m + 1 ) ! ( a 1 + a 2 + . . . a m ) ! ∗ a m + 1 ! = s u m 1 ! s u m 2 ! ∗ a m + 1 ! = C s u m 2 a m + 1 \frac{x_1}{x_0}= \frac{(a_1+a_2+...a_m+a_{m+1})! }{(a_1+a_2+...a_m)!*a_{m+1}!}=\frac{sum1!}{sum2!*a_{m+1}!}=C_{sum2}^{a_{m+1}} x0x1=(a1+a2+...am)!am+1!(a1+a2+...am+am+1)!=sum2!am+1!sum1!=Csum2am+1

    由此发现在原来的基础上加上 a m + 1 a_{m+1} am+1 个新质数后的排列组合数,和旧的之间的关系。

    因此可以先打组合数表作为备用。

  3. 那么这个sum最大会是多少呢,

    最糟糕的情况(在相同的n下) 是 s u m ! 1 ! ∗ 2 ! ∗ . . . . i ! \frac{sum!}{1!*2!*....i!} 1!2!....i!sum!,( s u m = ( 1 + i ) ∗ i / 2 sum=(1+i)*i/2 sum=(1+i)i/2) , 因为这时候分子最大

    而经过计算,在i到达20的时候,也就是sum到达80多的时候, s u m ! 1 ! ∗ 2 ! ∗ . . . . i ! \frac{sum!}{1!*2!*....i!} 1!2!....i!sum! 就会超过 2 63 2^{63} 263

    因此制作素数表的时候,只要找到前30个就好;并且也可以确定组合数表的范围 C b a C_b^a Cba , b<90

算法步骤
  1. 打素数表找30个素数,打组合数表,找底数小于100的组合数
  2. 为了让k尽可能小,那就是让前面的素数个数尽可能多。再利用dfs 减枝

代码

#include "bits/stdc++.h"
using namespace  std;
typedef unsigned long long ll;
const int N=100;
const int MAXN=1E6+10;
ll C[100][100];
int cnt=0,prime[N+10];
bool noprime[MAXN];
void Calumn()
{
    memset(C,0,sizeof(C));
    for(int i=1;i<=90;++i)   //通过估计,大概是80多就够用,所以取90
    {
        C[i][0] = 1;
        for (int j = 1; j <= i; ++j) {
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
        }
    }
}
void make_prim()
{
    cnt=0;
    int i;
    for(i=2; cnt<=30 ;++i)  //通过估计,大概只会用到20个素数,所以取30
    {
        if(noprime[i])continue;
        prime[++cnt]=i;
        for(int d=i+i;d<=MAXN;d+=i)noprime[d]=1;
    }
}
ll target,ans;
void dfs(int last,ll value,int n,int dep,ll ans_tem)
{
    // last 表示上一种素数用了几个,以此限制当前素数个数
    // value 表示当前的组合数是多少了
    // n 表示截至上一个,已经有了几个因数了,因为在求组合数时要用到
    // dep是深度的意思,表示当前是第几种因数了
    // ans_tem 表示这些质因数的价值,因为题目说要求最小的,因此用此来减枝条。
    if(dep>23)return;  //超过了估计使用的素数种数
    if(ans_tem>=ans)return;
    if(value==target){ans=ans_tem;return;}

    ll tem=1;
    for(int i=1;i<=last;++i)
    {
        tem*=prime[dep];
        if(ans_tem>(ans/tem)||C[n+i][i]>target )continue;

        ll n_value=value*C[n+i][i];
        if(n_value>target)continue;
        dfs(i,n_value,n+i,dep+1,ans_tem*tem);
    }
}
int main()
{
//    freopen("in.text","r",stdin);
    Calumn();
    make_prim();
    while (cin>>target)
    {
        ans=numeric_limits<ll>::max();
        dfs(63,1,0,1,1);
        cout<<target<<" "<<ans<<endl;
    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是Mally呀!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值