题解 CF736D 【Permutations】
iotang
·
·
题解
题目翻译
现在,你有一个二分图,点数为 2n。
已知这个二分图的完备匹配的个数是奇数。
现在你要知道,删除每条边后,完备匹配个数是奇数还是偶数。
## 如何解决它?
先把邻接矩阵搞出来,把它叫做 $G$,对于每个限制 $(a_i, b_i)$,让 $G_{a_i, b_i} = 1$。
可以看出,如果有一个合法的排列 $p_1, \dots , p_n$,那么 $G_{i, p_i}$ 都是 $1$。
有想到什么吗?
还记得行列式的式子吗?
你问我行列式前面 $(-1)^{???}$ 的系数怎么办?我们只关心奇偶性(对 $2$ 取模),$1$ 和 $-1$ 对我们来说是一样的,所以我们就可以不管它了。(其实这个东西叫做积和式)
所以,当邻接矩阵 $G$ 的行列式是奇数的时候,合法的排列数量也是奇数。
问题到现在,可以变成:把每个位置变成 $0$ 后,行列式是奇数还是偶数。
减去一个位置,行列式的变化怎么算?
大家应该都知道,有一个东西叫做代数余子式。$(-1)^{???}$ 的系数扔掉之后,只要看它的奇偶性就可以了。
$n$ 有 2000 诶,怎么把它们快速算出来?
大家应该都知道,有一个东西叫做伴随矩阵。求出伴随矩阵,就相当于求出所有位置的代数余子式。
因为 $G^{*} = G^{-1} |G|$,$|G|$ 是奇数(题面里面已经说了),那么只要求 $G^{-1}$ 就完事了!!1
$n$ 有 2000 诶,怎么把它快速算出来?
单走一个~~搞死乔丹算法~~高斯约当算法求个矩阵逆,就可以用神必技巧做到 $O(n^3 / 64)$(用 bitset 优化一下)甚至 $O(n^3)$。
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long lint;
typedef long double louble;
template<typename T1, typename T2> inline T1 max(T1 a, T2 b)
{
return a < b ? b : a;
}
template<typename T1, typename T2> inline T1 min(T1 a, T2 b)
{
return a < b ? a : b;
}
const char lf = '\n';
namespace ae86
{
const int bufl = 1 << 15;
char buf[bufl], *s = buf, *t = buf;
inline int fetch()
{
if (s == t)
{
t = (s = buf) + fread(buf, 1, bufl, stdin);
if (s == t)return EOF;
}
return *s++;
}
inline int ty()
{
int a = 0, b = 1, c = fetch();
while (!isdigit(c))b ^= c == '-', c = fetch();
while (isdigit(c))a = a * 10 + c - 48, c = fetch();
return b ? a : -a;
}
}
using ae86::ty;
const int _ = 2007, __ = _ + _, _e = 500007;
int n, m, ea[_e], eb[_e];
bitset<__> f[_];
int main()
{
ios::sync_with_stdio(0), cout.tie(nullptr);
n = ty(), m = ty();
for (int i = 1; i <= m; i++)ea[i] = ty(), eb[i] = ty(), f[ea[i]][eb[i]] = 1;
for (int i = 1; i <= n; i++)f[i][n + i] = 1;
for (int i = 1; i <= n; i++)
{
int a = i;
while (a < n && !f[a][i])a++;
if (!f[a][i])throw "c**a b***t";
swap(f[i], f[a]);
for (int j = 1; j <= n; j++)if (j != i && f[j][i])f[j] ^= f[i];
}
for (int i = 1; i <= m; i++)
cout << (f[eb[i]][ea[i] + n] ? "NO" : "YES") << lf;
return 0;
}
```
代码里和谐了一些不好的词语。