题意
给一个n∗mn*mn∗m 的格子矩阵,现在要在这些格子里面填数,填的数字是 [1,k][1,k][1,k] 中的整数,每个数可以填多次。定义好格子:一个格子所在的行和列中的每个格子中的数都严格小于该格子中的数。假设 AgA_gAg 为矩阵中好格子数量为 ggg 时填数的方案数,现在要输出 ∑g=0n∗m(g+1)Ag\sum_{g=0}^{n*m}(g+1)A_g∑g=0n∗m(g+1)Ag
分析
首先我们将输入出的表达式拆分开得到 ∑g=0n∗mgAg+∑g=0n∗mAg\sum_{g=0}^{n*m}gA_g+\sum_{g=0}^{n*m}A_g∑g=0n∗mgAg+∑g=0n∗mAg ,回顾一下 AgA_gAg 表示的是矩阵中有 ggg 个好格子的方案数。然后我们先看两个加数后面的那个部分表示的含义,直观上看这个和包括不存在好格子时的方案数;只有一个好格子时的方案数;有 n∗mn*mn∗m 个好格子时的方案数等。这样的话无论怎么填数,其结果都是上述情况中的一种。那么很显然这一部分的方案数应该是 Kn∗mK^{n*m}Kn∗m. 下面我们再看看前面这一部分,跟后面不一样的是这里所求的和的每一项都带了一个系数 ggg 表示有 ggg 个好格子时的方案数应该乘以该系数。这样我们不好直接找通项求和,那么我们现在换个思路求。我们将重心放到 n∗mn*mn∗m 个格子上,而不是原来的 ggg 上。意思就是我们看当第 xxx 行,第 yyy 列的这个格子是好格子的时候,他能填数的方案数时多少,再看看他对整个结果的贡献是多少。两者相乘,就可以作为答案的一部分。
这里强调一下,因为看的是每个格子的贡献,所以只要贡献算对了是不会有重复的。
下面这块有一个转化,举个例子说明。当只有一个格子是好格子的时候,按原来整体的思想答案应该是 A1A_1A1;假设格子1 (x1,y1)(x_1,y_1)(x1,y1) 和格子2 (x2,y2)(x_2,y_2)(x2,y2) 有且仅有这两块格子是好格子,很显然他们不可能在同一行或者同一列。那么当我们用原来的整体思想来看的话应该,此时的答案应该是 2∗A22*A_22∗A2 这时候我们可以将这两份 A2A_2A2 同时分给 格子1和格子2。再看如果只有三个好格子的情况,同样的原来的答案是 3∗A33*A_33∗A3, 我们可以把这三个分给那三个好格子。以此类推,对于每个格子 (x,y)(x,y)(x,y) 他的贡献应当是 ∑i=1m∗nAg\sum^{m*n}_{i=1}A_g∑i=1m∗nAg 。
这样我们我们会发现,如果 iii 从0开始那么就是随便乱摆,但是这里是从1开始的,那么我们应当保证至少有一个格子是好格子,其他的就可以随便乱摆了,分析和上面 iii 从0开始是一样的,这里就不细说了。现在我们来看好格子和与之同行同列的格子填数的种类,首先好格子至少必须填 222,其他的全填 111;好格子的填数范围是 [1,k][1,k][1,k] ,其他的只要比这个数小就行了,一共有 m−1+n−1m-1+n-1m−1+n−1 个数,每个数可以是 [1,i−1][1,i-1][1,i−1](iii是好格子的填的数)一共 i−1i-1i−1个数 。那么这一块的种类数就是 ∑i=2k(i−1)m−1+n−1\sum_{i=2}^{k}(i-1)^{m-1+n-1}∑i=2k(i−1)m−1+n−1。然后剩下的数就是随便乱填了,剩余 (n−1)∗(m−1)(n-1)*(m-1)(n−1)∗(m−1) 个数,每个数可以填 [1,k][1,k][1,k],所以是 k(n−1)∗(m−1)k^{(n-1)*(m-1)}k(n−1)∗(m−1) 种。最后我们只考虑了一个地方的好格子,一共有 m∗nm*nm∗n个地方,所以还要乘以 m∗nm*nm∗n。
综上,最后的答案就是这个
n∗m∑i=2k(i−1)n−1+m−1k(n−1)∗(m−1)+km∗nn*m\sum_{i=2}^{k}(i-1)^{n-1+m-1}k^{(n-1)*(m-1)}+k^{m*n}n∗mi=2∑k(i−1)n−1+m−1k(n−1)∗(m−1)+km∗n
代码
#include <iostream>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll pow_m(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) res = res * a%mod;
a = a * a%mod;
b >>= 1;
}
return res;
}
int main()
{
int t, ca = 1;
scanf("%d", &t);
while (t--)
{
ll n, m, k;
scanf("%lld%lld%lld", &n, &m, &k);
ll ans = 0;
for (int i = 2; i <= k; i++)
ans = (ans + pow_m(i - 1, n + m - 2)) % mod;
ans = ans * n*m%mod;;
ans = ans * pow_m(k, (n - 1)*(m - 1)) % mod;
ans = (ans + pow_m(k, m*n)) % mod;
printf("Case #%d: %lld\n",ca++, ans);
}
return 0;
}