DTOJ#5930

题解

考虑转化题意,即排列中的任意一段区间 [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 bab 所组成的图都具有传递性。

然后注意到任意一段区间不好求,发现等价于任意一段前缀边所组成的图满足传递性。

至此,题意转化为:有 nnn 个点,每次让你选两个不同的点连边,需要时刻保证整张图的传递性,求转移到两两之间都有连边的 DAGDAGDAG 的方案数。

有个性质,满足传递性的 DAGDAGDAG 种类只有 n!n!n! 种,且一种排列对于一个 DAGDAGDAG,具体地:对于一个排列 ppp,一对 (a,b)(a,b)(a,b)a<ba<ba<b,若 aaappp 中的位置在 bbb 后面,那么我们就把边 a→ba \to bab 连上,可以证明按照这种方式构建出来的 DAGDAGDAG 是满足传递性的,而且是双射。

然后我们其实就是要求从初始 DAGDAGDAG 转移到末态 DAGDAGDAG 的方案数,关于这玩意,我们在排列上 dpdpdp 因为排列好计数。

对于一条边 a→ba \to bab 若合法,想一下排列到 DAGDAGDAG 的构建过程,一定是 aaabbb 前面,然后变成 aaabbb 后面了,并且不影响其他边的状态,那么在排列上是怎么样的变化呢?也就是对于相邻的两个数 a,ba,ba,ba<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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值