• [NOI2021] 路径交点


    没去 ( t NOI) 现场的菜鸡(+)新高二退役狗来写一波题解。

    一、题目

    点此看题

    二、解法

    前置知识:行列式,矩阵乘法,高斯消元,比内(-)柯西公式。

    你看这题真是奇怪得不行,我以前做过这种题吗?没关系,我们来搞一些 ( t observation)

    • 选取的路径数正好是 (n_1),它并不是一个任意的数,而是求的一个几乎选满的方案,如果你做过这题就会知道这种特殊的组合问题可能会适用比内(-)柯西公式。
    • 本题求的方案是偶数交点的方案减去奇数交点的方案,那么一种方案的贡献是 ((-1)^{x}),再观察交点的定义式 ((p_j-q_j) imes(p_{j+1}-q_{j+1})<0),明显是逆序对个数啊,那么可以自然的联想到行列式。

    那么我们就从行列式的角度来解决这道题吧,首先考虑 (k=2) 的情况,设 (p_i) 表示 (X) 部第 (i) 个点匹配的 (Y) 部点是 (p_i),设 (f(p)) 表示排列 (p) 的逆序对个数,设 (g_{i,j}) 表示 ((i,j)) 是有边,我们通过枚举 (p) 可以暴力写出答案式:

    [sum_{p}(-1)^{f(p)}prod_{i=1}^{n_1} g_{i,p_i} ]

    那么不难发现就是矩阵 (g_{i,j}) 的行列式,因为我们已经会求二分图的情况,那么分层图的情况我们考虑把每一层一个一个合并上去,归约到二分图的情况,看图:

    所以比内(-)柯西公式帮助我们完成了从中间这一层 (m) 个点中选出 (n) 个的任务,选出点后两边可以分别匹配因为满足乘法原理,行列式也可以直接相乘。那么合并操作就相当于把邻接矩阵做矩阵乘法,那么算法流程是我们把每一层的邻接矩阵暴力乘起来,最后对 (n imes n) 的矩阵高斯消元,所得行列式即是答案,时间复杂度 (O(n^4))

    三、总结

    某位巨佬说:像这种偶数减奇数的题正好符合行列式模型,所以往那方面想。

    本题讨论二部图的行列式求法个人觉得很难,但和 (dp) 做法一样,都是从枚举到优化的过程,但你不去在思想上枚举是想不通的。分层图问题转化为二部图问题用到了归约的思想,值得借鉴!

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 205;
    const int MOD = 998244353;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n[M],m[M],k;
    struct mat
    {
    	int n,m,a[M][M];
    	void clear() {n=m=0;memset(a,0,sizeof a);}
    	mat() {clear();}
    	mat operator * (const mat &b) const
    	{
    		mat r;
    		r.n=n;r.m=b.m;
    		for(int i=1;i<=n;i++)
    			for(int j=1;j<=m;j++)
    				for(int k=1;k<=b.m;k++)
    					r.a[i][k]=(r.a[i][k]+a[i][j]*b.a[j][k])%MOD;
    		return r;
    	}
    }A,B;
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    int gauss(int n,int a[][M])
    {
    	int ans=1;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=i;j<=n;j++)
    			if(!a[i][i] && a[j][i])
    			{
    				swap(a[i],a[j]);
    				ans*=-1;
    				break;
    			}
    		ans=ans*a[i][i]%MOD;
    		for(int j=1;j<=n;j++)
    		{
    			if(i==j || !a[j][i]) continue;
    			int t=a[j][i]*qkpow(a[i][i],MOD-2)%MOD;
    			for(int k=1;k<=n;k++)
    				a[j][k]=(a[j][k]-a[i][k]*t)%MOD;
    		}
    	}
    	return (ans%MOD+MOD)%MOD;
    }
    void work()
    {
    	k=read();
    	for(int i=1;i<=k;i++) n[i]=read();
    	for(int i=1;i<k;i++) m[i]=read();
    	A.clear();A.n=n[1];A.m=n[2];
    	for(int i=1;i<=m[1];i++)
    	{
    		int u=read(),v=read();
    		A.a[u][v]=1;
    	}
    	for(int t=2;t<k;t++)
    	{
    		B.clear();
    		B.n=n[t];B.m=n[t+1];
    		for(int i=1;i<=m[t];i++)
    		{
    			int u=read(),v=read();
    			B.a[u][v]=1;
    		}
    		A=A*B;
    	}
    	printf("%lld
    ",gauss(n[1],A.a));
    }
    signed main()
    {
    	T=read();
    	while(T--) work();
    }
    
  • 相关阅读:
    vue 同时使用过渡和动画
    ie和火狐事件addEventListener()及attachEvent()区别分析
    闭包的理解
    实现Date函数属性中的format方法
    js读取cookie信息
    js解决千分符问题
    js脚本语言在页面上不执行
    split使用和特殊使用(包括截取第一个字符后的数据)
    js中请求数据的$post和$ajax区别(同步和异步问题)
    禁止页面拖拽事件(数据什么的)
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15063251.html
Copyright © 2020-2023  润新知