1.题目链接。题目大意:给定n,m。你可以在平面上选择一个任意大的k*k的矩阵,然后矩阵中的每一个格子上放置一定数量的玻璃球,每个格子玻璃球的数量不小于m,在这个矩阵中任意的选k个行列均不同的格子,他们包含地玻璃球的数量要都是相等的。问有多少种方案。
2.首先n,m都是大于等于1的,那么我们可以知道,k的最大值就是n,最小值是1,所以首先枚举k。对于每一个固定的k,又该如何解决呢?对于每一个固定的k,由于每个格子放置的数量不小于m,所以任选k个的最小值:k*m,最大值:n。那么我们就可以先写出求和式:
其中:
表示每个格子最少m个,矩阵大小为k,不同行列为T的方案数。那么我们首先简化一下这个式子,如果每个格子至少m个,那么我们把每个格子减去m,也就是每个格子至少0个,这个原问题到转化之后的问题的映射是线性映射,一一对应,所以转化后答案的数量不会变。那么在这种情况下,总和T变为:T-k*m。
那么原式子变为:
现在着手解决:f0(k,T-k*m).
问题的现在的表述为,现在有一个k*k的矩阵,我们有多少种方案在矩阵里面填数,填的数字都是大于等于0的数字,使得填完之后这个矩阵不同行列的和都相等,并且和为T(虽然上边是T-k*m,这里简单的写作T)。
(1)首先考虑怎么样填这个矩阵才能使得选择k个行列不同的格子和都相等?假设矩阵初始值全为0,这个时候满足条件,那么T=1怎么搞?我们考虑任意一行,这一行一定会有一个格子被选中,如果这个格子不是0,是1,那么他就对T有贡献,注意我们考虑该行的格子没有指定考虑哪一个?说明这一行的所有的格子都应该满足这个条件,其实这样就可以显然的得出一个结论:如果要达到任意选行列不同的k个和都相等,那么我们填的时候只能每次操作一整行或者一整列。比如T=1,我们可以任选一行,把这一行的所有格子+1,T=2,我们在T=1的基础上,再任选一行,把这一行的所有格子全部+1.那么问题来了,为什么你只操作行,不操作列?因为这是一个k*k的矩阵,也就是一个方阵,行列的地位都是一样的,或者说二者是等价的。OK,既然这样,我们可以把这个解决方案升级一下,对于总和为T,我们可以这样做:首先任选一行加上a1,任选一列加上b1.....也就是说,我们可以把总和T分割成2*k个元素,他们分别是:a1+a2...ak+b1+b2...bk=T,然后每一个ai,bi,我们任意把他们加到行上或者列上,这样加完之后,得到的矩阵就是满足条件的。那么可能你会有疑问,这样不会使得有的格子数量为0?是的,但是我们已经把问题转化了,转化为每个格子数量大于等于0了。这样问题又转化为:
有多少种分割方案,这就是个简单的问题了,相当于:x1+x2+...xn=b有多少种非负整数解的问题,就不再证明这里的结论了,这里不知道的可以参考《组合数学》机械工业出版社的,上面有着严格的证明。那么答案就是:C(2*k+T-1.2*k-1).这里可能有人会有疑问,对于每一组解,我们不还得把他们排列一下,看看到底加到哪一行?这里不用,因为这个解已经包含了所有情况,所以不需要再排列。
(2)那么上边的就是答案吗?仔细思考一下,有重复的地方。继续这个式子:a1+a2+...ak+b1+b2..bk=T.这里我们让每个a-1,减的1加到b上,那么中和还是不变的。啥意思?如果从最开始的填矩阵的方案考虑,对于每一行,我们让它减一,然后每一列+1,这样对于每个格子,本来行让它-1,但是列又给它+1了,所以每个格子的数量还是不变的,这样我们构造出的矩阵没有任何变,但是这时的,a,b是不同的,我们也把他们计算了一次。产生这种情况的原因是什么呢?究其本质,是ai>=1的时候,都会出现这种情况,并且我们刚才做的变化,也是一个单射,也就是说其实每个a1>=1,他都会被计算两次,第一次是它自己,第二次是它-1列+1.所以我们减去一次才是答案,那么对于所有ai>=1的方案如何计算呢?其实问题又转化为:
的方案数: 这个东西其实可以转化为上边的情况,让ai=ai-1,得到的:
这时候,ai又是非负数,转为为上边的问题:方案数:C(2*k+T-k-1,2*k-1)=C(k+T-1,2*k-1).
所以答案就是: C(2*k+T-1,2*k-1)-C(k+T-1,2*k-1).
到这里,本题已经做完。
using namespace std;
const int N = 6100;
ll mod = 998244353;
ll fac[N], inv[N];
ll qpow(ll a, ll b) {
ll res = 1;
while (b)
{
if (b & 1)res = res *a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll C(int n, int m) {
if (n < m)
return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
void init()
{
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i++)
{
fac[i] = fac[i - 1] * i % mod;
inv[i] = qpow(fac[i], mod - 2);
}
}
int main() {
int T;
scanf("%d", &T);
init();
while (T--) {
int n, m;
ll ans = 0;
scanf("%d%d", &n, &m);
for (int k = 1; k <= n; k++) {
int N = n - m * k;
if (N < 0)
break;
for (int T = 0; T <= N; T++) {
ans = (ans + C(2 * k - 1 + T, 2 * k - 1)) % mod;;
ans=(ans-C(k - 1 + T, 2 * k - 1)+mod)%mod;
}
}
printf("%lld\n", ans);
}
}