ACM学习历程—SNNUOJ 1116 A Simple Problem(递推 && 逆元 && 组合数学 && 快速幂)(2015陕西省大学生程序设计竞赛K题)

Description

Assuming a finite – radius “ball” which is on an N dimension is cut with a “knife” of N-1 dimension. How many pieces will the “ball” be cut into most?
However, it’s impossible to understand the following statement without any explanation.
Let me illustrate in detail.
When N = 1, the “ball” will degenerate into a line of finite length and the “knife” will degenerate into a point. And obviously, the line will be cut into d+1 pieces with d cutting times.
When N = 2, the “ball” will degenerate into a circle of finite radius and the “knife” will degenerate into a line. Likewise, the circle will be cut into (d^2+d+2)/2 pieces with d cutting times.
When N = 3, the “ball” will degenerate into a ball on a 3-dimension space and the “knife” will degenerate into a plane.
When N = 4, the “ball” will degenerate into a ball on a 4-dimension space and the “knife” will degenerate into a cube on a 3 dimension.
And so on.
So the problem is asked once again: Assuming a finite-radius “ball” which is on an N dimension is cut with a “knife” of N-1 dimension. How many “pieces” will the “ball” be cut into most?

Input

The first line of the input gives the number of test cases T. T test cases follow. T is about 300.
For each test case, there will be one line, which contains two integers N, d(1 <= N <= 10^5, 1 <= d <= 10^6).

Output

For each test case, output one line containing “Case #x: y”, where x is the test case number (standing from 1) and y is the maximum number of “pieces” modulo 10^9+7.

Sample Input

3

3 3

3 5

4 5

Sample Output

Case #1: 8

Case #2: 26

Case #3: 31

HINT

Please use %lld when using long long

 

题目大意就是n维空间切d刀,最多能分成几个部分。

基本上通过推倒三位的大概就能很快推出整个的递推式。

设p(n, d)表示n维空间切d刀。

假设已经切了d-1刀,最后一刀自然切得越多越好。于是最后一刀如果和所有d-1到切的话自然是最好。但是可以逆过来看,相当于d-1到切最后一刀这个n-1维空间。

于是p(n, d) = p(n, d-1) + p(n-1, d-1)

然而这个式子虽然出来了,但是根据n和d的范围打表是不可能的。也不能直接暴力递推求解,自然考虑到可能要直接求表达式。

然而,表达式我求了好久没求出来,不过看了最后表达式后,大概能有以下思路来求通项:

 

首先有以下事实:

1:手写打表的话:

 

d->

0

1

2

3

4

5

n

 

 

 

 

 

 

1

1

2

3

4

5

6

2

1

2

4

7

11

16

3

1

2

4

8

15

26

4

1

2

4

8

16

31

5

1

2

4

8

16

32

6

1

2

4

8

16

32

会发现当n >= d时,通项是2^d,其实稍微考虑一下确实如此。因为第一列都是1,自然第二列从第二项开始都是2,同理往后从对角线往后都是乘2,自然是2^d。

 

2:设p(n, d)的差数列为a(n, d)的话,

自然a(n, d) = p(n, d) – p(n-1, d)

由原式得p(n-1, d) = p(n-1, d-1) + p(n-2, d-1)

三式式消去p得

a(n, d) = a(n, d-1) + a(n-1, d-1)

说明p的差数列也是满足这个递推式,同理p的任意k阶差数列都满足这个式子。

然而让这些差数列最后通项不同的因素自然应该是前几项导致的

 

有了上面两个结论,于是只用求n < d的情况,可以从下面两个角度考虑

1:利用组合数式子:C(n, m) = C(n-1, m) + C(n-1, m-1),其中C(n, m)表示从n个中取m个。

由于这个式子和题目递推式非常形似。 于是猜测C(n, m)为p的某一阶差数列。根据前几列和前几行的计算,C(n, m)为p的第一阶差数列。于是p(n, d) = sum(C(d, i)) (0 <= i <= n)

 

2:根据第一个结论:列出第一阶的差数列

d->

0

1

2

3

4

5

n

 

 

 

 

 

 

1

 

1

2

3

4

5

2

0

0

1

3

6

10

3

0

0

0

1

4

10

4

0

0

0

0

1

5

5

0

0

0

0

0

1

6

0

0

0

0

0

0

基本上可以找规律,发现第一阶差数列是C(n, m)。

 

然后就是求C(d, i)的和了,由于d很大,考虑C(d, i) = A(d, i) / i!,然后就是求分子和分母在模10^9+7的情况下的商了。自然需要考虑到逆元。

这里对于逆元的处理可以预处理打表,经测试直接在线求exgcd逆元会T掉。

这里预处理用了网上的一个神奇的递推式,还有一种是我大连海事一个同学的做法。

 

代码(神奇式子):

#include <iostream>

#include <cstdio>

#include <cstdlib>

#include <cmath>

#include <cstring>

#include <algorithm>

#define LL long long

#define N 1000000007



using namespace std;



//快速幂

//O(logn)

LL quickPower(LL x, int n)

{

    x %= N;

    LL a = 1;

    while (n)

    {

        a *= n&1 ? x : 1;

        a %= N;

        n >>= 1;

        x = (x*x) % N;

    }

    return a;

}



LL c[100005], a[100005], inv[100005];

int n, d;



void init()

{

    //***预处理所有i在质数MOD下的逆元

    inv[1] = 1;

    for (int i = 2; i <= 100000; i++)

        inv[i] = inv[N%i]*(N-N/i) % N;



    a[0] = 1;

    for (int i = 1; i <= 100000; ++i)

        a[i] = (inv[i]*a[i-1]) % N;

}



void work()

{

    if (n >= d)

    {

        printf("%lld\n", quickPower(2, d));

        return;

    }

    LL now = d, ans = 0;

    c[0] = 1;

    for (int i = 1; i <= n; ++i)

    {

        c[i] = (now*c[i-1]) % N;

        now--;

    }

    for (int i = 0; i <= n; ++i)

    {

        ans += c[i]*a[i];

        ans %= N;

    }

    printf("%lld\n", ans);

}



int main()

{

    //freopen("test.in", "r", stdin);

    //freopen("test.out", "w", stdout);

    init();

    int T;

    scanf("%d", &T);

    for (int times = 1; times <= T; ++times)

    {

        printf("Case #%d: ", times);

        scanf("%d%d", &n, &d);

        work();

    }

    return 0;

}
View Code

代码二(exgcd):

#include <iostream>

#include <cstdio>

#include <cstring>

#include <cmath>

#include <algorithm>

#include <queue>

#define FOR(i,x,y)  for(int i = x;i < y;i ++)

#define IFOR(i,x,y) for(int i = x;i > y;i --)

#define ll  long long

#define N   111111

#define D   1111111

#define MOD 1000000007



using namespace std;



ll c[N],mu[N];

ll n,d;



ll quickpow(ll a,ll n,ll m){

    ll ans=1;

    while(n){

        if(n&1) ans = (ans*a)%m;

        a = (a*a)%m;

        n>>=1;

    }

    return ans;

}



void ex_gcd(ll a,ll b,ll& d,ll& x,ll& y){

    if(!b)  {d = a;x = 1;y = 0;return;}

    ex_gcd(b,a%b,d,y,x);

    y -= x*(a/b);

}



ll inv(ll a,ll n){

    ll d,x,y;

    ex_gcd(a,n,d,x,y);

    return d == 1 ? (x+n)%n : -1;

}



void init(){

    FOR(i,1,N){

        mu[i] = inv(i,MOD);

    }

}



void C(){

    c[0] = 1;

    FOR(i,1,n+1){

        ll tem = (d+1-i)*mu[i]%MOD;

        c[i] = (tem*c[i-1]) % MOD;

    }

}



ll solve(){

    ll res = 0;

    FOR(i,0,n+1){

        res += c[i];

        res %= MOD;

    }

    return res;

}



int main()

{

    //freopen("test.in","r",stdin);

    int t,tCase = 0;

    scanf("%d",&t);

    init();

    while(t--){

        printf("Case #%d: ",++tCase);

        scanf("%lld%lld",&n,&d);

        ll ans = 0;

        if(n >= d){

            ans = quickpow(2,d,MOD);

        }

        else{

            C();

            ans = solve();

        }

        printf("%lld\n",ans);

    }

    return 0;

}
View Code

 

你可能感兴趣的:(simple)