方格计数
- description
- solution
- code
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)-1gcd(x,y)−1(不包含线段的两个整数端点)
有nnn个盒子,从中选mmm个,且相邻两个盒子之间至少有kkk个盒子的组合方案为(n−k(m−1)m)\binom{n-k(m-1)}{m}(mn−k(m−1))
证明:mmm个盒子会造成m−1m-1m−1个长度至少为kkk的盒子空隙,减去这(m−1)k(m-1)k(m−1)k个盒子,剩下的就相当于是随便选位置了
首先,暴力的想法,枚举两个端点,横坐标之差的绝对值为xxx,纵坐标之差的绝对值为yyy,那么这里面包含的整数点个数为g−1,g=gcd(x,y)g-1,g=\gcd(x,y)g−1,g=gcd(x,y)
强制两个端点必须选,那么剩下还要选n−2n-2n−2个
求出相邻两个盒子之间编号差至少为kkk(利用两点间距离公式和题目要求的ddd),即两个盒子之间的盒子个数至少为k−1k-1k−1
两个端点选了会导致两个k−1k-1k−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)即可
但是这个差是绝对值差,所以如果不是水平或竖直线,就有两种情况(可以理解为yyy有正负,一条指向左方的线对称有指向右方的线;也可以理解为xxx有正负,一条指向下方的线对称有一条指向上方的线)
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;
}