传送门: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,...,k−1,如果满足
(
p
i
−
q
i
)
∗
(
p
i
+
1
+
q
i
+
1
)
<
0
(p_i-q_i)*(p_{i+1}+q_{i+1})<0
(pi−qi)∗(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
1∼n的排列),那么这组路径的逆序数的奇偶性就恰好是排列
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
8∗106个数,因此你可以尝试一下
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;
}
最后吐槽:说真的,这题挺没意思的。