zoukankan      html  css  js  c++  java
  • 【难】组合数学+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
    ");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
    ",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?'
    ':' ');
        printf("
    ");
    
    }
    View Code
  • 相关阅读:
    powermock测试
    一些疑惑
    Java基础总结3
    Java学习路线
    Java基础总结2
    关于我
    翻转单词序列
    和为s的两个数字
    和为s的连续正数序列
    数组中只出现一次的数字
  • 原文地址:https://www.cnblogs.com/zsben991126/p/12841712.html
Copyright © 2011-2022 走看看