【难】组合数学+dp——ICPC PNWRC 2019

两篇讲的比较清楚的博客(感觉比官方题解讲的清楚些)

https://blog.csdn.net/The___Flash/article/details/105931836

https://blog.csdn.net/monochrome00/article/details/105921913/

思路:这类题最常规的思路是从第一位开始按位确定,假设当前在第i位,即前i-1个数已经确定的情况下求字典序第k‘小的数

那么我们就要知道第i位选择填j时的方案数是否会>=k'

所以需要预处理出dp[i][j][k]:i个数(不一定是排列数),有j个数在自己位置上,有k个数不可能找到自己的位置的方案数(想想为什么要设置状态k)

那么设第i位选择j后,前i位已经有x个数在自己位置上,后n-i个数中有y个数不可能找到自己位置对应的方案数是 dp[n-i][m-x][y],

  如果>=k',那么第i位就可以确定是j

 

所以关键是要求这个dp[i][j][k],有两种方案:

第一种是直接去记忆化搜索(官方题解)

第二种是再设一个辅助状态:g[i][j]表示i个数,有j个元素不可能找到自己位置的方案数(博客题解)

#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;
               ans[i]=j;vis[j]=true;
                 wn=nwn;
                break;
            }
        }
    }
    for(int i=1;i<=n;i++) printf("%d%c",ans[i],i==n?'\n':' ');
    printf("\n");

}
View Code

 

posted on 2020-05-07 11:18  zsben  阅读(137)  评论(0编辑  收藏  举报

导航