[NOI2021D1T2]路径交点

壹、题目描述 ¶

传送门 to Luogu.

贰、题解

◆ 前言

本篇题解并没有使用 \(\rm LGV\) 引理 毕竟我也不会那东西,但是涉及 \(\rm Binet-Cauchy\) 公式。

◆ 从简单想起

想一个简单的问题 —— 如果 \(k=2\),也就是我们只有两边的点怎么处理?

似乎还不是很好求,那么我们继续简化 —— 如果我们只求合法的方案数而并非奇偶作差怎么办?

似乎没什么正解的思路?—— 那就想一想暴力一点的。

好,于是我们有一个很简单的暴力思路 —— 使用 \(\mathcal O(n!)\) 枚举全排列 \(p\),对于每一位 \(i\),假设填数 \(p(i)\),表示这个方案有一条 \(i\rightarrow p_i\) 的边(第一层连向第二层),然后检查这个方案是否合法。

\(G\) 为这两层图之间的邻接矩阵,那么这个最简化的问题就是

\[\sum_{\sigma\text{ is a permutation}}\prod_{i=1}^n G(i,\sigma_i) \]

这个东西的本质是矩阵 \(G\) 的积和式,目前面对求解一个一般矩阵的积和式的问题,我们只有对矩阵使用拉布拉斯展开在复杂度 \(\mathcal O(n!)\)​ 下计算......所以这个问题从本质上说是变难了,不过却有助于我们向正解靠拢。

\(f(\sigma)\) 为排列 \(\sigma\) 这个方案交点个数,那么奇偶交点的方案数作差就是

\[\sum_{\sigma\text{ is a permutation}}(-1)^{f(\sigma)}\prod_{i=1}^n G(i,\sigma_i) \]

发现这东西和行列式有点像?现在我们希望的是满足:

\[f(\sigma)=\tau(\sigma) \]

事实上,这俩确实满足这个等式,这就不必我多说了吧.......

我们重新观察这个式子,根据行列式的定义,这事实上就是 \(\det(G)\).

◆ 再多一层?

我们已经成功解决 \(k=2\) 的情形,那么,如果 \(k=3\),我们会面临什么更多的问题?

  • 相邻两层的点数或将不同;

\(G_i\) 为第 \(i\) 层与第 \(i+1\)​ 层的邻接矩阵,此时我们想要将简单版的思路推广,即我们希望能够证明,在这个问题下,存在

\[Ans=\det\left(G_1\times G_2\right) \]

其中 \(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\times G_2)=\sum_{i_1<i_2<i_3<\cdots<i_{n_1}}\det G_1(123\cdots n;i_1,i_2,i_3,\cdots,i_n)\times \det G_2(i_1,i_2,i_3,\cdots,i_n;123\cdots n) \]

其中 \(\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)\)​ 边都存在)以及交点的奇偶性!

显然,这就是我们想求的,通过含义进行分析,我们可以得到

\[Ans=\det(G_1\times G_2) \]

◆ 😃 结论!

有三层,那么我们可以推广到多层的情况,此时显然有

\[Ans=\det\left(\prod_{i=1}^{k-1}G_i\right) \]

令人害怕的是,该算法复杂度达到惊人的 \(\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\) 方向想想。

posted @ 2021-07-26 20:36  Arextre  阅读(146)  评论(0编辑  收藏  举报