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;
}

浙公网安备 33010602011771号