题解
考虑转化题意,即排列中的任意一段区间 [ l , r ] [l,r] [l,r],都不能:存在 ( a , b ) , ( b , c ) (a,b),(b,c) (a,b),(b,c),而不存在 ( a , c ) (a,c) (a,c)。
也就是具有传递性,即若有 ( a , b ) , ( b , c ) (a,b),(b,c) (a,b),(b,c) 那么一定有 ( a , c ) (a,c) (a,c),反过来也是。
所以问题就转化为求一个排列(点对的),使得排列中的任意一段区间中的点对 ( a , b ) (a,b) (a,b) 所代表的边 a → b a \to b a→b 所组成的图都具有传递性。
然后注意到任意一段区间不好求,发现等价于任意一段前缀边所组成的图满足传递性。
至此,题意转化为:有 n n n 个点,每次让你选两个不同的点连边,需要时刻保证整张图的传递性,求转移到两两之间都有连边的 D A G DAG DAG 的方案数。
有个性质,满足传递性的 D A G DAG DAG 种类只有 n ! n! n! 种,且一种排列对于一个 D A G DAG DAG,具体地:对于一个排列 p p p,一对 ( a , b ) (a,b) (a,b), a < b a<b a<b,若 a a a 在 p p p 中的位置在 b b b 后面,那么我们就把边 a → b a \to b a→b 连上,可以证明按照这种方式构建出来的 D A G DAG DAG 是满足传递性的,而且是双射。
然后我们其实就是要求从初始 D A G DAG DAG 转移到末态 D A G DAG DAG 的方案数,关于这玩意,我们在排列上 d p dp dp 因为排列好计数。
对于一条边 a → b a \to b a→b 若合法,想一下排列到 D A G DAG DAG 的构建过程,一定是 a a a 在 b b b 前面,然后变成 a a a 在 b b b 后面了,并且不影响其他边的状态,那么在排列上是怎么样的变化呢?也就是对于相邻的两个数 a , b a,b a,b 且 a < b a<b a<b ,交换 a , b a,b a,b 就代表连接合法的 ( a , b ) (a,b) (a,b)。
最后,由于常数问题,转移建议使用非递归版。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
const int P=998244353;
int n,m,jc[15],a[15],f[N],mp[55][55],tot,pos[15];
vector<int> e[1005];
struct Node{
int a,b;
}p[10005];
inline int get(){
int k=0;
for(int i=1;i<=n;++i){
int s=0;
for(int j=1;j<i;++j)if(a[j]<a[i])s++;
s=a[i]-1-s;
k+=s*jc[n-i];
}
return k+1;
}
inline int qm(int x){
return (x>=P)?x-P:x;
}
int main(){
scanf("%d%d",&n,&m);
jc[0]=1;for(int i=1;i<=n;++i)jc[i]=jc[i-1]*i;
for(int i=1;i<=n;++i)a[i]=n-i+1;
for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)mp[i][j]=++tot,p[tot]=(Node){i,j};
for(int i=1;i<=m;++i){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
e[mp[c][d]].push_back(mp[a][b]);
}
while(1){
int k=get();
if(k==jc[n])f[k]=1;
else{
for(int i=1;i<=n;++i)pos[a[i]]=i;
for(int i=1;i<n;++i){
if(a[i]>a[i+1])continue;
int fg=0,x=mp[a[i]][a[i+1]];
for(int j=0;j<e[x].size();++j){
if(pos[p[e[x][j]].a]<pos[p[e[x][j]].b]){
fg=1;
break;
}
}
if(!fg){
swap(a[i],a[i+1]);
int y=get();
f[k]=qm(f[k]+f[y]);
swap(a[i],a[i+1]);
}
}
}
if(k==1)break;
prev_permutation(a+1,a+n+1);
}
printf("%d\n",f[1]);
return 0;
}