2024.2.18模拟赛T2 题解

当我们把二元组看作边之后,我们发现题目形式可以转化为,对任意的区间,都满足他构成的图是传递闭包

进一步可以转化为,对每一个前后缀都满足,证明可以用反证法

接着可以发现,对于一种选边方案,他和排列构成双射,将排列中的逆序对全部选出来,构成边,就可得到选边方案

所以一共就只有 \(n!\) 种状态数 , 可以考虑dp,那那些特殊限制可以在转移的时候,看是否出现过即可

要特别注意的是,记录状态数的时候要用康托展开

code

#include<bits/stdc++.h>
using namespace std;
#define N 1505
#define int long long
int n,m,tot;
const int mod=998244353;
vector<int> qi[N];
int cnt[N],pos[N],num[N],cc[N],jc[N],f[3628805];
int ok(int i){
	if(num[i]>num[i+1]) return 0;
	int x=num[i]*11+num[i+1];
	for(auto y:qi[x]){
		if(pos[y/11]<pos[y%11]) return 0;
	}
	return 1;
}
int find(int x){
	int y=0;
	for(int i=3;i>=0;i--){
		if(y+(1<<i)>n) continue;
		if(cc[y+(1<<i)]<x) x-=cc[y+(1<<i)],y+=(1<<i);
	}
	return y;
}
int lowbit(int x){
	return x&(-x);
}
void add(int x,int y){
	for(;x<=n;x+=lowbit(x)) cc[x]+=y;
}
void gt_num(int p){
	p--;
	for(int i=1;i<=n;i++) cnt[i]=pos[i]=num[i]=cc[i]=0;
	for(int i=1;i<=n;i++) add(i,1);
	for(int i=1;i<=n;i++){
		cnt[i]=p/jc[n-i];p-=cnt[i]*jc[n-i];
		num[i]=find(cnt[i]+1)+1;pos[num[i]]=i,add(num[i],-1);
	}
}
signed main(){
	freopen("perm.in","r",stdin);
	freopen("perm.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1,a,b,c,d;i<=m;i++){
		scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
		qi[c*11+d].push_back(a*11+b);
	}
	jc[0]=1;
	for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i;
	tot=jc[n];
	f[1]=1;
	for(int i=1;i<tot;i++){
		gt_num(i);
		for(int j=1;j<n;j++){
			if(!ok(j)) continue;
			int np=i+(cnt[j+1]+1-cnt[j])*jc[n-j]+(cnt[j]-cnt[j+1])*jc[n-j-1];
			f[np]+=f[i],f[np]%=mod;
		}
	}
	printf("%lld\n",f[tot]);
	return 0;
}
posted @ 2024-02-19 08:51  hubingshan  阅读(27)  评论(0)    收藏  举报