题解:AT_agc067_d [AGC067D] Unique Matching
似乎之前没有和该做法一样的题解。
本文中,为了保护视力 (*^_^*),将用中括号包裹部分索引。
对于一个区间序列
-
S$ ***对应*** $P
当且仅当
这题要我们做的就是,对所有长为
排列很烦,先把排列强制为
这样,问题就变成了
- 求有多少个区间序列
S ,满足S 对应且仅对应排列\{1,2,\dots,n\} 。
现在来考虑一个问题,假设我们已经获得了一个区间序列
不妨画一个方阵
(对于命题
例如,区间序列为
容易发现,区间序列和方阵是一一对应的。
对于一个区间序列
- 存在一个坐标
(x,x) ,满足\mathbf{M}[x][x]=1 ,且\forall j\neq x ,有\mathbf{M}[j][x]=0 ,且删除\mathbf{M} 中(x,x) 所在行和所在列后,得到的方阵仍满足此条件。
正确性是容易验证的。这个条件是递归性的,并且可以 dp。
但是,如果存在多个可以第一次删除的坐标怎么办呢?考虑容斥,钦定一些位置可以在第一次删除。
设
然后可以用“辅助数组”
当然,有初始值
直接 dp 空间复杂度为
代码:
#include<bits/stdc++.h>
using std::cerr; using std::setw; using std::endl;
using std::cin; using std::cout;
template<typename Tp>
bool tomax(Tp &x,const Tp &y){if(x<y){x=y; return 1;} return 0;}
template<typename Tp>
bool tomin(Tp &x,const Tp &y){if(y<x){x=y; return 1;} return 0;}
using ll=long long; using ui=unsigned; using lf=double;
using ull=unsigned long long;
constexpr ll MAXN=5000;
ll N,P;
ll mo(ll x){return x>=P?x-P:x;}
void n_add(ll &x,ll y){x=mo(x+y); return ;}
void n_sub(ll &x,ll y){x=mo(x+P-y); return ;}
ll pow2(ll x){return x*x%P;}
ll fac[MAXN+5];
ll g[MAXN+5],h[MAXN+5];
int main(){
std::ios::sync_with_stdio(0); std::cin.tie(0); std::cout.tie(0);
cin>>N>>P;
fac[0]=1ll; for(ll i=1;i<=N;i++) fac[i]=i*fac[i-1]%P;
g[0]=1ll;
for(ll i=1;i<=N;i++){
h[i]=i*g[i-1]%P;
for(ll j=1;j<i;j++){
n_sub(h[i],h[j]*pow2(i-j)%P*g[i-j-1]%P);
}
for(ll j=1;j<=i;j++){
n_add(g[i],h[j]*(i-j+1)%P*g[i-j]%P);
}
}
ll ans=g[N]*fac[N]%P;
cout<<ans<<'\n';
return 0;
}