其他分享
首页 > 其他分享> > 骗我呢

骗我呢

作者:互联网

题意:

求有多少个 \(n × m\) 的矩阵满足

首先观察到 \(a_{i,j}\) 的取值范围以及矩阵的列,得出矩阵的每一行必然有一个在 \(0\) ~ \(m\) 之间的数是不存在的。

通过第二个矩阵需要满足的条件得出每一行除去这个不存在的数,这个序列是单调递增的。

设第 \(i\) 行这个不存在的数为 \(b_{i}\),那么看看 \(b_i\) 与 \(a_{i, j}\) 的关系。

根据矩阵每一行单调递增的性质,不难得出:

\(a_{i, j}\) \(=\) \(j\) \(-\) \(1\) \(+\) \([\) \(b_i\) \(≤\) \(j\) \(]\);

再将这个式子代入矩阵需要满足的第三个条件得出:

\(j\) \(-\) \(1\) \(+\) \([\) \(b_i\) \(≤\) \(j\) \(]\) \(<\) \(j\) \(+\) \(1\) \(-\) \(1\) \(+\) \([\) \(b_{i - 1}\) \(≤\) \(j\) \(+\) \(1\) \(]\);

化简可得:

\([\) \(b_i\) \(≤\) \(j\) \(]\) \(<\) \(1\) \(+\) \([\) \(b_{i - 1}\) \(≤\) \(j\) \(+\) \(1\) \(]\);

因此可以知道:

当 \(b_i\) \(≤\) \(j\) 成立时 \(b_{i - 1}\) \(≤\) \(j\) \(+\) \(1\) 一定也成立。

进而可得:

\(b_i\) \(≥\) \(b_{i - 1}\) \(-\) \(1\);

得出这个关系后,我们设 \(f_{i, j}\) 表示第 \(i\) 行中值为 \(j\) 的数不存在所能构成的矩阵的个数。

根据上面 \(b_i\) 和 \(b_{i-1}\) 的关系式,不难得出 \(f_{i,j}\) 的转移方程:

\(f_{i,j}\) \(=\) \(\sum_{k = 0}^{j + 1}\) \(f_{i-1, k}\);

将这个方程展开化简得出:

\(f_{i, j}\) \(=\) \(f_{i, j - 1}\) \(+\) \(f_{i - 1, j + 1}\);

边界条件:

\(f_{i, 0}\) \(=\) \(f_{i - 1, 0}\) \(+\) \(f_{i - 1, 1}\);

根据上面的最开始得出的转移方程可以知道最终答案就是 \(f_{n + 1, m - 1}\)

将转移式子转化为一张图可得:

图1

发现图的左边与中间并不整齐,因此再转化:

图2

因此此题此时转化为了从左上角的 \((0,\) \(-1)\) 号点,每次可以向左下或者向右走,不能走出图的边界,走到画绿挑的点 \((2,\) \(2)\) 号点(到蓝挑点也一样)的方案数。

首先只有向左下走一种方法可以到达下一层,而我们一共需要下 \(n\) 层,因此向左下走的步数就为 \(n\),且这样走一定不会越出矩阵下面或上面的边界。

此时也相当于我们一共向左也走了 \(n\) 步,而为了到达右端的 \(m\) \(-\) \(1\) 点,我们还需要向右走 \(n\) \(+\) \(m\) 步,因此问题再次转化为:

在一个数轴上,从 \(-1\) 出发,向左走 \(n\) 步,向右走 \(n\) \(+\) \(m\) 步,不越过 \([\) \(-1,\) \(m\) \(]\) 这个范围,求方案数。

为了方便,将整体向右平移一位,问题最终变为:

在一个数轴上,从原点出发,向左走 \(n\) 步,向右走 \(n\) \(+\) \(m\) 步,不越过 \([\) \(0,\) \(m\) \(+\) \(1\) \(]\) 这个范围,求方案数。

考虑采用反射的方法,以点 \(-1\) 为中心对称,以 \(m\) \(+\) \(2\) 为中心进行对称,而这个方法的基础可以在 这道题 学习。

相较于普通的采用反射法的题来说,此题最大的不同就是需要进行反复容斥,即我们不仅需要求出越过原点的方案数,还需要求出先越过 \(m\) \(+\) \(1\) 再越过原点的方案数,再就还需要求出先越过原点再越过 \(m\) \(+\) \(1\) 再越过原点的方案数,因此可以递归求解。

以先横跨左端点为例:

设 \(crossL(l,\) \(r,\) \(end)\) 表示当前经过若干次反射得到的限制区间为 \(l\) ~ \(r\),需要到达的终点为 \(end\),的方案数。

那么它的返回值就是从 \(0\) 到 \(end\) 走 \(2n\) \(+\) \(m\) 步的方案数 \(-\) 再跨一遍左(右)区间的方案数。

设向左走 \(a\) 步,向右走 \(b\) 步到达 \(end\)(这里 \(a\),\(b\) 是啥都无所谓)

那么可得方程:

\(\begin{cases}a+b=2n + m\\ a-b=end\\ \end{cases}\)

解得

\(a\) \(=\) \(n\) \(+\) \(\frac{m + end}{2}\);

所以总的方案数就为 \(C(n\) \(+\) \(\frac{m + end}{2},\) \(2n\) \(+\) \(m)\);

当然还需要减去 \(crossL(2\) \(\times\) \((l\) \(-\) \(1)\) \(-\) \(r,\) \(2\) \(\times\) \((l\) \(-\) \(1)\) \(-\) \(l,\) \(2\) \(\times\) \((l\) \(-\) \(1)\) \(-\) \(end)\)。这是因为对于每一次反射,区间左端点就变为了右端点,右端点就变为了左端点,因此我们只需不断的按照当前的左端点进行反射即可。

具体见 code:

#include <cstdio>
#include <cmath>

typedef long long ll;

const int MAXN = 1e6;
const ll MOD = 1e9 + 7;

ll n, m;
ll fac[3 * MAXN + 10], invFac[3 * MAXN + 10];

ll quickPow(ll x, ll k) {

	ll ans = 1ll;
	while(k) {
		if(k & 1) {
			ans = ans * x % MOD;
		}
		k >>= 1;
		x = x * x % MOD;
	}

	return ans;
}

ll C(ll mm, ll nn) {
	return fac[nn] * invFac[mm] % MOD * invFac[nn - mm] % MOD;
}

ll crossR(ll l, ll r, ll end) {
	if(abs(end) > 2 * n + m) return 0ll; //判断终点能否到达 
	return (C(n + ((m + end) >> 1), 2 * n + m) - crossR(2 * (r + 1) - r, 2 * (r + 1) - l, 2 * (r + 1) - end)) % MOD;
}

ll crossL(ll l, ll r, ll end) {
	if(abs(end) > 2 * n + m) return 0ll;
	return (C(n + ((m + end) >> 1), 2 * n + m) - crossL(2 * (l - 1) - r, 2 * (l - 1) - l, 2 * (l - 1) - end)) % MOD;
}

int main() {

	scanf("%lld %lld", &n, &m);
	
	fac[0] = 1ll, invFac[0] = 1ll;
	fac[1] = 1ll, invFac[1] = 1ll;
	for(int i = 2; i <= 2 * n + m; ++i) { //求阶乘和其逆元,以便于 O(1) 计算组合数 
		fac[i] = fac[i - 1] * i % MOD;
		invFac[i] = quickPow(fac[i], MOD - 2);
	}
		
	ll ans = ((crossR(0, m + 1, m) + crossL(0, m + 1, m)) % MOD - C(n + m, 2 * n + m)) % MOD; //第一次横跨L和R把中间这一段算了两遍,因此要再减一下 
	
	printf("%lld\n", (ans + MOD) % MOD);

	return 0;
}

标签:,end,ll,矩阵,端点,return,MOD
来源: https://www.cnblogs.com/louis660/p/16586016.html