2021牛客NOIP提高组第二场T2——方格计数(组合数计数)

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} (mnk(m1))

证明: m m m个盒子会造成 m − 1 m-1 m1个长度至少为 k k k的盒子空隙,减去这 ( m − 1 ) k (m-1)k (m1)k个盒子,剩下的就相当于是随便选位置了


首先,暴力的想法,枚举两个端点,横坐标之差的绝对值为 x x x,纵坐标之差的绝对值为 y y y,那么这里面包含的整数点个数为 g − 1 , g = gcd ⁡ ( x , y ) g-1,g=\gcd(x,y) g1,g=gcd(x,y)

强制两个端点必须选,那么剩下还要选 n − 2 n-2 n2

求出相邻两个盒子之间编号差至少为 k k k(利用两点间距离公式和题目要求的 d d d),即两个盒子之间的盒子个数至少为 k − 1 k-1 k1

两个端点选了会导致两个 k − 1 k-1 k1的长度区间盒子不能选

剩下的盒子数只有 g − 1 − 2 ( k − 1 ) g-1-2(k-1) g12(k1)

套用上面的组合数公式,即 ( g − 1 − 2 ( k − 1 ) − ( n − 3 ) ( k − 1 ) n − 2 ) \binom{g-1-2(k-1)-(n-3)(k-1)}{n-2} (n2g12(k1)(n3)(k1))

不难发现,最后只与横纵坐标差有关,所以直接枚举横纵坐标差,乘以情况数 ( w − x + 1 ) ( h − y + 1 ) (w-x+1)(h-y+1) (wx+1)(hy+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;
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值