题解
考虑转化题意,即排列中的任意一段区间 [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→ba \to ba→b 所组成的图都具有传递性。
然后注意到任意一段区间不好求,发现等价于任意一段前缀边所组成的图满足传递性。
至此,题意转化为:有 nnn 个点,每次让你选两个不同的点连边,需要时刻保证整张图的传递性,求转移到两两之间都有连边的 DAGDAGDAG 的方案数。
有个性质,满足传递性的 DAGDAGDAG 种类只有 n!n!n! 种,且一种排列对于一个 DAGDAGDAG,具体地:对于一个排列 ppp,一对 (a,b)(a,b)(a,b),a<ba<ba<b,若 aaa 在 ppp 中的位置在 bbb 后面,那么我们就把边 a→ba \to ba→b 连上,可以证明按照这种方式构建出来的 DAGDAGDAG 是满足传递性的,而且是双射。
然后我们其实就是要求从初始 DAGDAGDAG 转移到末态 DAGDAGDAG 的方案数,关于这玩意,我们在排列上 dpdpdp 因为排列好计数。
对于一条边 a→ba \to ba→b 若合法,想一下排列到 DAGDAGDAG 的构建过程,一定是 aaa 在 bbb 前面,然后变成 aaa 在 bbb 后面了,并且不影响其他边的状态,那么在排列上是怎么样的变化呢?也就是对于相邻的两个数 a,ba,ba,b 且 a<ba<ba<b ,交换 a,ba,ba,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;
}
633

被折叠的 条评论
为什么被折叠?



