AT_agc030_f [AGC030F] Permutation and Minimum 题解
先去掉相邻两个都填完的位置,对于两个都没填的记个数为 \(c\),最后只需要将答案乘上 \(c!\)。
接下来考虑从小到大枚举所有数进行 dp,记 \(f_{i,j,k}\) 表示考虑完前 \(1\sim i\),有 \(j\) 个数需要跟一个位置确定的数匹配,有 \(k\) 个数需要跟后面一个自由的数匹配,考虑当前的数:
- 如果已经确定位置,则有转移 \(f_{i-1,j,k}\to f_{i,j-1,k}\),\(f_{i-1,j,k}\to f_{i,j,k+1}\)。
- 如果没有确定位置,则有转移 \(f_{i-1,j,k}\to f_{i,j,k-1}\),\(f_{i-1,j,k}\cdot(j+1)\to f_{i,j+1,k}\),\(f_{i-1,j,k}\to f_{i,j,k+1}\)。
直接转移即可,最后的答案即为 \(f_{2n,0,0}\cdot c!\),时间复杂度 \(\mathcal O(n^3)\)。
参考代码:
#include<bits/stdc++.h>
#define ll long long
#define md 1000000007
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define rept(i,a,b) for(int i=(a);i<(b);++i)
#define drep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
int n,ct,a[603],dp[603][303][303];
bool b[603],c[603];
ll ans;
signed main(){
scanf("%d",&n);
rep(i,1,n<<1)scanf("%d",&a[i]);
for(int i=1;i<=n<<1;i+=2){
if(a[i]==-1&&a[i+1]==-1)ct++;
else if(a[i]==-1)b[a[i+1]]=1;
else if(a[i+1]==-1)b[a[i]]=1;
else c[a[i]]=c[a[i+1]]=1;
}
dp[0][0][0]=1;
rep(i,1,n<<1){
rep(j,0,n)rep(k,0,n)if(dp[i-1][j][k]){
if(c[i]){
dp[i][j][k]=dp[i-1][j][k];
continue;
}
if(b[i]){
if(j)dp[i][j-1][k]=(dp[i][j-1][k]+dp[i-1][j][k])%md;
dp[i][j][k+1]=(dp[i][j][k+1]+dp[i-1][j][k])%md;
}else{
if(k)dp[i][j][k-1]=(dp[i][j][k-1]+dp[i-1][j][k])%md;
dp[i][j+1][k]=(dp[i][j+1][k]+dp[i-1][j][k]*(j+1ll))%md;
dp[i][j][k+1]=(dp[i][j][k+1]+dp[i-1][j][k])%md;
}
}
}
ans=dp[n<<1][0][0];
rep(i,1,ct)ans=ans*i%md;
cout<<ans;
return 0;
}