[luogu7736] [NOI2021] 路径交点 - 线性代数 - 矩阵乘法 - 行列式

传送门:https://www.luogu.com.cn/problem/P7736
题目大意:有一张 k k k层的图,每相邻两层之间有一些有向边,保证第一层和最后一层节点数一样多(均为 n n n个)。
定义一组合法的路径形如:共 n n n条路径,第 i i i条从第一层第 i i i个点出发,连向最后一层的某个点,且所有路径终点以及中间经过的所有点均互不相同。
定义两条路径的“逆序数”是:如果两条路径经过的点依次是 p 1 , . . . , p k p_1,...,p_k p1,...,pk q 1 , . . . , q k q_1,...,q_k q1,...,qk,对于每个 i = 1 , . . . , k − 1 i=1,...,k-1 i=1,...,k1,如果满足 ( p i − q i ) ∗ ( p i + 1 + q i + 1 ) < 0 (p_i-q_i)*(p_{i+1}+q_{i+1})<0 (piqi)(pi+1+qi+1)<0,就对“逆序数”有 1 1 1的贡献。你可以直观地理解为如果把整张图画在纸面上,两条路径的交点个数。
定义一组路径的“逆序数”为任意两条不同路径的“逆序数”之和。
对于所有合法的路径组,求逆序数为偶数的路径组比逆序数为奇数的路径组多多少?

这题其实感觉题面在说很多废话,比如对于“逆序数”的定义,其实挺繁琐的:因为很容易观察到题目考察的内容——逆序数的奇偶性——只与两条路径的起点与终点有关系。
更进一步,不妨设这 n n n条路径的起点是 1 , 2 , . . . , n 1,2,...,n 1,2,...,n,终点是 p 1 , . . . , p n p_1,...,p_n p1,...,pn(一个 1 ∼ n 1\thicksim n 1n的排列),那么这组路径的逆序数的奇偶性就恰好是排列 p p p的逆序对个数的奇偶性(或者直接称为排列 p p p的奇偶性)。
而“偶排列比奇排列多多少个”这种问题我们再熟悉不过了:行列式!显然,如果 k = 2 k=2 k=2,那么直接对着邻接矩阵求行列式就是答案。
如果 k k k更大?一个简单的想法当然是,如果我求出 f i j f_{ij} fij表示从第一层第 i i i个点到最后一层第 j j j个点的路径条数(容易想到 f f f的求法就是把每一层的邻接矩阵乘起来),然后对着这个矩阵做行列式不就完了?
然后你就去写了一发,并且过了看起来人畜无害的几组样例,就在你点击“提交”的一瞬间,突然一个激灵:“要求所有路径在中间不经过重复的顶点”这个条件怎么没用上?
你已经打算重新开始推式子了,再一抬头,“???怎么AC了?”(如果你没有不幸被卡常的话)
没错,直接这么做就是对的!接下来我不去讲LGV引理、柯西比内定理之类的东西(说实话我之前也没听说过LGV引理这个东西),我就从直观上描述:
如果我们允许道路在中间交叉,会发生什么?
假设有两条路径,一条从第一层 x i x_i xi连向最后一层 y i y_i yi,另一条从第一层 x j x_j xj连向最后一层 y j y_j yj。两条路径在中间某层某个点 p p p处相交。
那么对于任意包含这两条路径的一组路径,一定会有另一组路径与之对应:这两组路径的其他路径完全相同,只有这两条路径不一样——在第二组中,这两条路径在点 p p p后互相交换,于是第一条路径从第一层 x i x_i xi连向最后一层 y j y_j yj,第二条路径从第一层 x j x_j xj连向最后一层 p i p_i pi
容易看出这两组路径的“逆序数”一定一奇一偶(容易证明下列引理:对于任意排列 p p p,交换其中任意两个位置之后,排列的奇偶性一定改变)。
于是,我们很容易在所有含有相交路径的路径组之间建立一一配对的关系,从而所有这样的路径组对于答案的贡献会被全部抵消!
想明白这一点,这个题就极其简单了。
顺带一提,CF167E跟这道题几乎是一模一样的思路和做法。今年NOI怎么这么多原题啊qwq
最后说一句,如果你不幸被卡常了,请注意这题的输入最多有 8 ∗ 1 0 6 8*10^6 8106个数,因此你可以尝试一下 f r e a d fread fread读入;如果还不行的话,注意到矩阵乘法过程中,每次乘的邻接矩阵的元素只有 0 0 0 1 1 1,因此你完全不需要每次做乘法就取一次模,而是每做完一次(甚至 4 4 4次其实都可以)矩阵乘法之后整体取一次模;最后你可以试试改变枚举顺序(从 i j k ijk ijk改成 i k j ikj ikj)之类的,不过这就挺玄学了,而且个人感觉加上取模优化就完全够用了。
复杂度显然是 O ( n 3 k ) O(n^3k) O(n3k)
代码如下:

#include<bits/stdc++.h>
char buf[100000],*buff = buf + 100000;
#define gc ((buff == buf + 100000 ? (fread(buf,1,100000,stdin),buff = buf) : 0),*(buff++))
#define li long long
#define pc putchar
using namespace std;
const int mo = 998244353;
inline int read(){
	int x = 0,c = gc;
	while(c < '0' || c > '9') c = gc;
	while(c >= '0' && c <= '9') x = x * 10 + c - '0',c = gc;
	return x;
}
inline void print(int x){
	if(x >= 10) print(x / 10);
	pc(x % 10 + '0');
}
int T,k,n[105],m[105];
struct mtx{
	li a[205][205];
	int x,y;
	inline li* operator [](int x){return a[x];}
	inline mtx(){x = y = 0;memset(a,0,sizeof(a));}
	inline void init(){x = y = 0;memset(a,0,sizeof(a));}
}a[105];
inline mtx operator * (mtx x,mtx y){
	assert(x.y == y.x);
	mtx as;as.x = x.x;as.y = y.y;
	li tmp;
	register int i,j,k;
	for(i = 1;i <= x.x;++i) for(k = 1;k <= x.y;++k){
		tmp = x[i][k];
		for(j = 1;j <= y.y;++j)
			as[i][j] += tmp * y[k][j];
	} 
	for(i = 1;i <= x.x;++i) for(j = 1;j <= y.y;++j) if(as[i][j] > 4e16l) as[i][j] %= mo;
	return as;
}
inline li ksm(li q,li w){
	li as = 1;
	while(w){
		if(w & 1) as = as * q % mo;
		q = q * q % mo;
		w >>= 1;
	}
	return as;
} 
inline li det(mtx x){
	assert(x.x == x.y);
	int n = x.x,i,j,k;
	for(i = 1;i <= n;++i) for(j = 1;j <= n;++j) x[i][j] %= mo;
	li tmp = 1;
	for(i = 1;i <= n;++i){
		if(!x[i][i]){
			for(j = i + 1;j <= n;++j) if(x[j][i]){
				tmp = (mo - tmp) % mo;
				for(k = i;k <= n;++k) swap(x[i][k],x[j][k]);
				break;
			}
		}
		if(!x[i][i]) return 0;
		(tmp *= x[i][i]) %= mo;
		li tp = ksm(x[i][i],mo - 2);
		for(j = i;j <= n;++j) (x[i][j] *= tp) %= mo;
		for(j = i + 1;j <= n;++j){
			tp = x[j][i];
			for(k = i + 1;k <= n;++k) (x[j][k] += mo - x[i][k] * tp % mo) %= mo;
			x[j][i] = 0;
		}
	}
	return tmp;
}
int main(){
	int i,j,u,v;
	T = read();
	while(T--){
		k = read();
		for(i = 1;i < k;++i) a[i].init();
		for(i = 1;i <= k;++i) n[i] = read(),a[i].x = a[i - 1].y = n[i];
		for(i = 1;i < k;++i) m[i] = read();
		for(i = 1;i < k;++i){
			for(j = 1;j <= m[i];++j){
				u = read();v = read();
				a[i][u][v] = 1;
			}
		}
		for(i = 2;i < k;++i) a[1] = a[1] * a[i];
		print(det(a[1]));pc('\n');
	}
	return 0;
}

最后吐槽:说真的,这题挺没意思的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值