Another Filling the Grid
题目链接:
题目大意:
给定一个矩形,在其中填数,要满足每一行,每一列的最小值都为1。
解法
一.容斥解法
1.解法I
我们先单独考虑每一行,先计算出每一行的合法方案数,再去计算整个问题的合法方案数,这样就从一个直接从二维解决的问题变成了一个一维的问题。
那么对于每一行的方案数,我们发现如果说直接进行计算答案,从行上面考虑还是一个裸的容斥,复杂度是 \(O(n)\),那么我们不如直接考虑不合法的方案数,即每个位置填的数均大于 \(2\) 即可,那么此时的方案数即为 \((k-1)^n\),那么合法方案数也就出来了,即为 \(k^n-(k-1)^n\),复杂度 \(O(1)\)。
那么现在就可以计算总的方案数了,我们发现对于每一列也是一个容斥,我们直接钦定有 \(i\) 列不满足条件,那么此时的方案数就是
上面式子的组合意义为:在 \(n\) 列里面选出 \(i\) 列不选 \(1\),然后有总的不合法的方案数就是 \((k-1)^n\),满足上面条件的方案数但不一定满足行的方案数为 \(k^{n-i}(k-1)^{i}\),那么满足上面的条件,并且满足行的条件是 \(k^{n-i}(k-1)^i - (k-1)^n\)。
那么很明显容斥就出来了:
那么我们就可以在 \(nlogn\) 的时间复杂度内求解了。
解法II
我们直接进行整体容斥,那么我们可以很容易得出:
组合意义为:我们钦定 \(i\) 行,\(j\) 列不满足条件,那么对应的组合数为 \(\binom{n}{i} \binom{n}{j}\),然后钦定的只有 \(k-1\) 种填法,没钦定的有 \(k\) 种填法,然后把每个格子的填法乘起来就是方案数了。
那么现在复杂度是 \(O(n^2 logn)\) 的,我们只需要预处理就可以变成 \(n^2\) 了,但是这还不够!!! 既然解法I 能做到 \(nlogn\) ,那么我们考虑优化上边的式子,我们先将 \(\binom{n}{i}\) 和 \((-1)^i\) 提到第一个求和号后面:
我们发现后边有求和号并且具有组合数,我们考虑用二项式定理进行优化。那么我们就学要对后面进行变形
那么应用二项式定理就变成了
竟然和上边的式子一样的!!!那么我们现在也可以 \(nlogn\) 求解了。
反演解法
首先我们明确我们可以通过二项式反演来完成在恰好与至多至少之间的转换。
那么我们考虑二维反演,我们令 \(F_{i,j}\) 为有 \(i\) 行 ,\(j\) 列不满足的方案数, \(G_{i,j}\) 为恰好 \(i\) 行,\(j\) 列不满足的方案数,根据多维反演的结论,我们可以知道:
对于 \(F_{i,j}\) 的求法和优化方法上面已经说过了,这里就不再说了。
Code
#include<bits/stdc++.h>
#define int long long
const int mod = 1e9 + 7 , M = 300;
int fac[M] , inv[M] , pow_k[M] , pow_k1[M];
inline int Pow(int a ,int b) {
int ans = 1; for(; b; b >>= 1 , a = a * a % mod) if(b & 1) ans = ans * a % mod;
return ans;
}
inline int C(int n , int m) {
if(n < m || n < 0 || m < 0) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main () {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr),std::cout.tie(nullptr);
int n , k; std::cin >> n >> k;
fac[0] = inv[0] = pow_k[0] = pow_k1[0] = 1;
for(int i = 1; i <= n; ++ i) fac[i] = fac[i - 1] * i % mod , inv[i] = Pow(fac[i] , mod - 2) , pow_k[i] = pow_k[i - 1] * k % mod , pow_k1[i] = pow_k1[i - 1] * (k - 1) % mod;
int ans = 0;
for(int i = 0; i <= n; ++ i) ans = (ans + (i & 1 ? mod-1 : 1) * (C(n , i) * Pow(pow_k[n - i] * pow_k1[i] % mod + mod - pow_k1[n] % mod , n) % mod)) % mod;
std::cout << ans << '\n';
}
总结
对于一道容斥反演题,我们可以从多个角度去进行思考:
- 对原问题进行一维一维的分析,达到降维的目的
- 从最根本的容斥做起,用组合意义,组合恒等式来推式子
- 直接利用反演进行求解
参考文章:
zxy大佬的blog
zzx大佬的blog