description
在左下角是 (𝟎, 𝟎),右上角是 (W, H)的网格上,有 (W + 1) × (H + 1) 个格点。
现在要在格点上找 N个不同的点,使得这些点在一条直线上。并且在这条直线上,
相邻点之间的距离不小于𝐃。求方案数模 109 + 7
【输入格式】
第一行一个整数 𝐓,表示数据组数。
接下来 𝐓 行,每行四个整数 N, W, H, D,意义如题目描述。
【输出格式】
𝐓 行,每行一个整数表示答案。
【样例 1 输入】
7
2 2 3 3
1 4 5 3
1 251 497 2
5 40 28 10
2 2 2 2
18 60 58 2
19 2 58 4
【样例 1 输出】
9
30
125496
597
16
172006701
0
【数据范围】
对于 100% 的数据, 1 ≤ N ≤ 50, 1 ≤ W, H, D ≤ 500, 1 ≤ T ≤ 20
solution
在一个以 ( 0 , 0 ) (0,0) (0,0)开始的二维网格中,一条线段 ( 0 , 0 ) − ( x , y ) (0,0)-(x,y) (0,0)−(x,y)含有整数点的个数为 gcd ( x , y ) − 1 \gcd(x,y)-1 gcd(x,y)−1(不包含线段的两个整数端点)
有 n n n个盒子,从中选 m m m个,且相邻两个盒子之间至少有 k k k个盒子的组合方案为 ( n − k ( m − 1 ) m ) \binom{n-k(m-1)}{m} (mn−k(m−1))
证明: m m m个盒子会造成 m − 1 m-1 m−1个长度至少为 k k k的盒子空隙,减去这 ( m − 1 ) k (m-1)k (m−1)k个盒子,剩下的就相当于是随便选位置了
首先,暴力的想法,枚举两个端点,横坐标之差的绝对值为 x x x,纵坐标之差的绝对值为 y y y,那么这里面包含的整数点个数为 g − 1 , g = gcd ( x , y ) g-1,g=\gcd(x,y) g−1,g=gcd(x,y)
强制两个端点必须选,那么剩下还要选 n − 2 n-2 n−2个
求出相邻两个盒子之间编号差至少为 k k k(利用两点间距离公式和题目要求的 d d d),即两个盒子之间的盒子个数至少为 k − 1 k-1 k−1
两个端点选了会导致两个 k − 1 k-1 k−1的长度区间盒子不能选
剩下的盒子数只有 g − 1 − 2 ( k − 1 ) g-1-2(k-1) g−1−2(k−1)
套用上面的组合数公式,即 ( g − 1 − 2 ( k − 1 ) − ( n − 3 ) ( k − 1 ) n − 2 ) \binom{g-1-2(k-1)-(n-3)(k-1)}{n-2} (n−2g−1−2(k−1)−(n−3)(k−1))
不难发现,最后只与横纵坐标差有关,所以直接枚举横纵坐标差,乘以情况数 ( w − x + 1 ) ( h − y + 1 ) (w-x+1)(h-y+1) (w−x+1)(h−y+1)即可
但是这个差是绝对值差,所以如果不是水平或竖直线,就有两种情况(可以理解为 y y y有正负,一条指向左方的线对称有指向右方的线;也可以理解为 x x x有正负,一条指向下方的线对称有一条指向上方的线)
code
#include <cstdio>
#include <cmath>
using namespace std;
#define maxn 505
#define int long long
#define mod 1000000007
int T, n, w, h, d;
int c[maxn][maxn];
void init() {
for( int i = 0;i < maxn;i ++ ) {
c[i][0] = c[i][i] = 1;
for( int j = 1;j < i;j ++ )
c[i][j] = ( c[i - 1][j] + c[i - 1][j - 1] ) % mod;
}
}
int gcd( int x, int y ) {
if( ! y ) return x;
else return gcd( y, x % y );
}
double calc( int x, int y ) {
return sqrt( x * x * 1.0 + y * y );
}
int solve( int x, int y ) {
if( ! x and ! y ) return 0;
int g = gcd( x, y );
int k = ( int )ceil( d / calc( x / g, y / g ) );
if( k * ( n - 1 ) > g ) return 0;
int ans = c[g - 1 - 2 * ( k - 1 ) - ( k - 1 ) * ( n - 3 )][n - 2];
if( x and y ) ans = ( ans << 1 ) % mod;
return ans * ( w - x + 1 ) % mod * ( h - y + 1 ) % mod;
}
signed main() {
init();
scanf( "%lld", &T );
while( T -- ) {
scanf( "%lld %lld %lld %lld", &n, &w, &h, &d );
if( n == 1 ) {
printf( "%lld\n", ( w + 1 ) * ( h + 1 ) );
continue;
}
int ans = 0;
for( int i = 0;i <= w;i ++ )
for( int j = 0;j <= h;j ++ )
ans = ( ans + solve( i, j ) ) % mod;
printf( "%lld\n", ans );
}
return 0;
}