标签:
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; }
代码二(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; }
ACM学习历程—SNNUOJ 1110 A Simple Problem(递推 && 逆元 && 组合数学 && 快速幂)(2015陕西省大学生程序设计竞赛K题)
标签:
原文地址:http://www.cnblogs.com/andyqsmart/p/4571352.html