[NOI2021D1T2]路径交点
壹、题目描述 ¶
贰、题解 ¶
◆ 前言
本篇题解并没有使用 \(\rm LGV\) 引理 毕竟我也不会那东西,但是涉及 \(\rm Binet-Cauchy\) 公式。
◆ 从简单想起
想一个简单的问题 —— 如果 \(k=2\),也就是我们只有两边的点怎么处理?
似乎还不是很好求,那么我们继续简化 —— 如果我们只求合法的方案数而并非奇偶作差怎么办?
似乎没什么正解的思路?—— 那就想一想暴力一点的。
好,于是我们有一个很简单的暴力思路 —— 使用 \(\mathcal O(n!)\) 枚举全排列 \(p\),对于每一位 \(i\),假设填数 \(p(i)\),表示这个方案有一条 \(i\rightarrow p_i\) 的边(第一层连向第二层),然后检查这个方案是否合法。
设 \(G\) 为这两层图之间的邻接矩阵,那么这个最简化的问题就是
这个东西的本质是矩阵 \(G\) 的积和式,目前面对求解一个一般矩阵的积和式的问题,我们只有对矩阵使用拉布拉斯展开在复杂度 \(\mathcal O(n!)\) 下计算......所以这个问题从本质上说是变难了,不过却有助于我们向正解靠拢。
设 \(f(\sigma)\) 为排列 \(\sigma\) 这个方案交点个数,那么奇偶交点的方案数作差就是
发现这东西和行列式有点像?现在我们希望的是满足:
事实上,这俩确实满足这个等式,这就不必我多说了吧.......
我们重新观察这个式子,根据行列式的定义,这事实上就是 \(\det(G)\).
◆ 再多一层?
我们已经成功解决 \(k=2\) 的情形,那么,如果 \(k=3\),我们会面临什么更多的问题?
- 相邻两层的点数或将不同;
设 \(G_i\) 为第 \(i\) 层与第 \(i+1\) 层的邻接矩阵,此时我们想要将简单版的思路推广,即我们希望能够证明,在这个问题下,存在
其中 \(G_1\) 为 \(n_1\times n_2\) 的矩阵,\(G_2\) 为 \(n_2\times n_3=n_1\times n_1\) 的矩阵。
由于 \(G_1,G_2\) 并非完美的方阵,而我们又要求行列式,这不禁让我们想到 \(\rm Binet-Cauchy\) 公式,使用该公式对此行列式进行展开,得到:
其中 \(\det G_1(123\cdots n;i_1,i_2,i_3,\cdots,i_n)\) 表示由 \(G_1\) 的第 \(1,2,3,\cdots,n\) 行以及 \(i_1,i_2,i_3,\cdots i_n\) 列构成的子矩阵。
分析这个式子的含义,第一项即连接第一层中所有 \((x,i_x)\),并且带上这一层中交点的奇偶性,第二项即连接所有 \((i_y,y)\),并且带上这一层的交点的奇偶性,那么他们俩乘起来?就是这个方案是否存在(满足所有 \((x,i_x)\) 以及 \((i_y,y)\) 边都存在)以及交点的奇偶性!
显然,这就是我们想求的,通过含义进行分析,我们可以得到
◆ 😃 结论!
有三层,那么我们可以推广到多层的情况,此时显然有
令人害怕的是,该算法复杂度达到惊人的 \(\mathcal O(T(kn^3+n^2\log))\),不过跑得还蛮快的嘛......
◆ 多说一句
为什么该题让我们求偶数方案减去奇数方案呢?其实上文提到过,如果是求方案之和的话,那转化的方向就是矩阵的积和式了,然而目前只能使用 \(\mathcal O(n!)\) 的算法解决......并不比暴力优秀多少,甚至还更难打。
叁、参考代码 ¶
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define NDUBUG
#include<cassert>
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxk=100;
const int Mod=998244353;
template<class T>struct matrix{
vector< vector<T> >a; int n, m;
inline matrix(){ n=m=0; a.clear(); }
inline matrix(int N, int M, T v=0): n(N), m(M){
a.resize(n);
for(int i=0; i<n; ++i)
a[i].resize(m, v);
}
inline matrix Epsilon(const int N){
(*this)=matrix<T>(N, N, 0);
for(int i=0; i<n; ++i) a[i][i]=1;
return (*this);
}
inline matrix operator *(const matrix rhs) const{
assert(m==rhs.n);
matrix<T>ret(n, rhs.m);
for(int i=0; i<n; ++i) for(int j=0; j<m; ++j) if(a[i][j])
for(int k=0; k<rhs.m; ++k)
ret.a[i][k]=(ret.a[i][k]+1ll*a[i][j]*rhs.a[j][k]%Mod)%Mod;
return ret;
}
inline int det(){
assert(n==m); int ret=1;
vector< vector<T> >Mat=a;
for(int i=0; i<n; ++i){
for(int j=i+1; j<n; ++j) while(Mat[j][i]){
int t=Mat[i][i]/Mat[j][i];
for(int k=i; k<n; ++k)
Mat[i][k]=(Mat[i][k]+Mod-1ll*Mat[j][k]*t%Mod)%Mod;
swap(Mat[i], Mat[j]), ret=-ret;
}
if(!Mat[i][i]) return 0;
ret=1ll*ret*Mat[i][i]%Mod;
}
return (ret+Mod)%Mod;
}
};
matrix<int>ans, G;
int n[maxk+5], m[maxk+5], k;
inline void solve(){
k=readin(1);
for(int i=0; i<k; ++i)
n[i]=readin(1);
for(int i=1; i<k; ++i)
m[i]=readin(1);
ans=matrix<int>().Epsilon(n[0]);
int u, v;
for(int i=1; i<k; ++i){
G=matrix<int>(n[i-1], n[i], 0);
for(int j=1; j<=m[i]; ++j){
u=readin(1)-1, v=readin(1)-1;
G.a[u][v]=1;
}
ans=ans*G;
}
writc(ans.det());
}
signed main(){
// freopen("xpath.in", "r", stdin);
// freopen("xpath.out", "w", stdout);
rep(_, 1, readin(1)) solve();
return 0;
}
肆、关键之处 ¶
左右都有 \(\mathcal n\) 个点的二部图的完美匹配方案数就是该二部图邻接矩阵的积和式,以及奇偶作差时,不妨往 \((-1)^k\) 方向想想。