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 → b a \to b ab 所组成的图都具有传递性。

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

至此,题意转化为:有 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 ab 连上,可以证明按照这种方式构建出来的 D A G DAG DAG 是满足传递性的,而且是双射。

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

对于一条边 a → b a \to b ab 若合法,想一下排列到 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值