题目链接https://www.jisuanke.com/contest/8291?view=challenges
题意
询问长度为 n n n ,满足 m m m 个下标与数值匹配的,字典序第k小的排序。
题解
大概也就写了一万个
d
p
dp
dp 吧。
c
[
i
]
[
j
]
c[i][j]
c[i][j] 表示
i
i
i 个位置挑出
j
j
j 个的方案数,也就是组合数。
f
a
c
[
i
]
fac[i]
fac[i] 表示
i
i
i 的阶乘。
f
[
i
]
f[i]
f[i] 表示
i
i
i 个位置的错排方案数。
g
[
i
]
[
j
]
g[i][j]
g[i][j] 表示
i
i
i 个位置,
j
j
j 个数字不与任何下标匹配的错排方案数。
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 表示
i
i
i 个位置,
j
j
j 个位置应该匹配,
k
k
k 个数字不与任何下标匹配的方案数。
前面三个很容易算出,第四个我的转移比较复杂:
i
i
i 个位置,
j
j
j 个数字不与任何下标匹配(我们称作通配符),也就是说另外有
i
−
j
i-j
i−j 个数字存在对应下标匹配(称作非通配符)
- 首先枚举 k k k ,表示从通配符中选出 k k k 个放在非通配符的位置上: c [ j ] [ k ] c[j][k] c[j][k]
- 非通配符中选出 k k k 个位置放通配符: c [ i − j ] [ k ] c[i-j][k] c[i−j][k]
- 再从这 k k k 个非通配符数挑出 l l l 个放在通配符位置上: c [ k ] [ l ] c[k][l] c[k][l]
- 同样的也要从没被选中的非通配符中挑出 k − l k-l k−l 个放在通配符位置上: c [ i − j − k ] [ k − l ] c[i-j-k][k-l] c[i−j−k][k−l]。
- 现在在通配符位置上的 j j j 个数全排列(原本就在通配符上的通配符,和换进来的非通配符一起全排列): f a c [ j ] fac[j] fac[j]
- 换出去的通配符也可以在挑出来的 k k k 个位置上全排列: f a c [ k ] fac[k] fac[k]
- 现在我们已经确立了 j + k j+k j+k 个位置,还剩 i − j − k i-j-k i−j−k 个。其中原本 i − j i-j i−j 个非通配符有 k − l k-l k−l 个被占了位置,它们也变成了新的通配符。所以转移到 g [ i − j − k ] [ k − l ] g[i-j-k][k-l] g[i−j−k][k−l]。
有关 g g g 的全部转移就在这了:
g[i][j]+=c[j][k]*c[i-j][k]*c[k][l]*c[i-j-k][k-l]*fac[j]*fac[k]*g[i-j-k][k-l];
然后是
d
p
dp
dp 的转移:
这个简单一些,从
i
−
k
i-k
i−k 个非通配符中挑出
j
j
j 个满足匹配,剩下就从
g
g
g 转移:
dp[i][j][k]=c[i-k][j]*g[i-j][k];
有了这个 d p dp dp 之后,只需要每一位一个个枚举,根据方案数来填数字即可。还有如果方案过大,直接设置成无穷就好。
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
const int N=3e5+7;
const ll inf=1e18+1e17;
ll c[60][60];
ll f[60];
ll fac[60];
ll dp[60][60][60];
ll g[60][60];
ll n,m,k;
bool vis[60];
int ans[60];
int main()
{
for(int i=0;i<=50;i++){
for(int j=0;j<=i;j++){
if(i==j||j==0) c[i][j]=1;
else c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
f[0]=f[2]=1;
for(int i=3;i<=50;i++){
if(i<=20) f[i]=f[i-1]*(i-1)+f[i-2]*(i-1);
else f[i]=inf;
}
fac[0]=1;
for(ll i=1;i<=50;i++){
if(i<=21) fac[i]=fac[i-1]*i;
else fac[i]=inf;
}
for(int i=0;i<=50;i++){
for(int j=0;j<=i;j++){
if(j==0){g[i][j]=f[i];continue;}
for(int k=0;k<=j&&k<=i-j;k++){
for(int l=max(0,-i+j+k+k);l<=k;l++){
///挑k个出去,外面挑k个位置,外面位置对应的数中挑l个换进来,外面没被挑中的另外挑k-l个进来,进来的都可以全排列,外面位置全排列放通配符
if(inf/c[j][k]/c[i-j][k]/c[k][l]/c[i-j-k][k-l]/fac[j]/fac[k]<g[i-j-k][k-l]) g[i][j]=inf;
else if(g[i][j]+c[j][k]*c[i-j][k]*c[k][l]*c[i-j-k][k-l]*fac[j]*fac[k]*g[i-j-k][k-l]>inf) g[i][j]=inf;
else g[i][j]+=c[j][k]*c[i-j][k]*c[k][l]*c[i-j-k][k-l]*fac[j]*fac[k]*g[i-j-k][k-l];
}
}
}
}
for(int i=0;i<=50;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=i-j;k++){
if(inf/c[i-k][j]<g[i-j][k]) dp[i][j][k]=inf;
else dp[i][j][k]=c[i-k][j]*g[i-j][k];
}
}
}
scanf("%lld%lld%lld",&n,&m,&k);
if(dp[n][m][0]<k){printf("-1\n");return 0;}
/// k--;
int fix=m,wn=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(vis[j]) continue;
if(fix==0&&i==j) continue;
int nwn=wn;
int nfix=fix;
if(i==j) nfix--;
else if(!vis[i]) nwn++;
if(j<i) nwn--;
///printf("i=%d j=%d wn=%d nwn=%d fix=%d nfix=%d k=%lld dp=%lld\n",i,j,wn,nwn,fix,nfix,k,dp[n-i][nfix][nwn]);
if(k>dp[n-i][nfix][nwn]) k-=dp[n-i][nfix][nwn];
else{
fix=nfix;
wn=nwn;
ans[i]=j;vis[j]=true;
break;
}
}
}
for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
printf("\n");
}
/**
50 0 1000000000000000000
48 0 1000000000000000000
*/