• 计数DP


    前置芝士

    好像也没啥( ̄▽ ̄)"

    (组合数还是要会求的)

    作用

    ​ 统计对象的可能情况一般比较多,通常需要精确的划分和整体性的计算。因此,使用动态规划抽象出问题中的“子结构”和推导的“阶段”,将有助于我们准确而高效地进行求解。

    ​ 这些问题往往都起源于排列组合中的组合公式 (C_n^k=C_{n-1}^k+C_{n-1}^{k-1})

    例题1

    划分数
    思路

    (dp[i][j])(j) 个物品 划分 (i) 次的方案数

    可得到: (dp[i][j]=dp[i][j-i]+dp[i-1][j])

    意思是将 (j) 个物品分成 (i) 份,有两种情况: (dp[i][j-i]) 每份划分都大于等于 (1) ; (dp[i-1][j]) 存在有一份以上用 (0) 划分

    代码
    #include<iostream>
    #include<cstdio>
    
    using namespace std;
    int n,m;
    int dp[1200][1200];
    
    int main(){
        scanf("%d%d",&n,&m);
        dp[0][0]=1;
        for(int i=1;i<=m;i++)
            for(int j=0;j<=n;j++){
                if(j>=i) dp[i][j]=dp[i][j-i]+dp[i-1][j];
                else dp[i][j]=dp[i-1][j];
            }
        printf("%d",dp[m][n]);
        return 0;
    }
    

    (其实也可以直接拿组合数做, (C_{m-n+1}^n) 就出来了,讲DP是为了思维)

    例题2

    CF559C Gerald and Giant Chess
    思路

    ​ 先考虑如果没有黑色格子的时候,我们的方案数是 (C_{h+w-2}^{h-1}) ,若再求出左上到右下经过至少一个黑色格子的路线数量,相减就得到了只经过白色格子的路线数。

    ​ 然后考虑具体做法:把所有黑色格子按照行,列坐标递增的顺排序。并且把左上角和右下角看作黑色格子。设第 (i) 个格子在第 (x_i) 行第 (y_i) 列。设 (F[i]) 表示从左上角走到第 (i) 个黑色格子,途中不经过其他黑色格子的路线数。

    [F[i]=C_{x_i-1+y_i-1}^{x_i-1}-sumlimits_{j=0}^{i-1}f[j]*C_{x_i-x_j+y_i-y_j}^{x_i-x_j}~~(x_igeq x_j,y_igeq y_j) ]

    这就是状态转移方程了。

    代码
    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #define int long long
    
    using namespace std;
    const int N=3e5+50,mod=1e9+7;
    int h,w,n,p[N],inv[N],f[N];
    struct node{
        int x,y;
    }a[N];
    bool cmp(node a,node b){
        return a.x<b.x||(a.x==b.x&&a.y<b.y);
    }
    
    int C(int m,int n){
        if(m>n) return 0;
        return (p[n]*inv[n-m]%mod*inv[m])%mod;
    }
    
    inline int fpow(int a,int b){
        int res=1;
        while(b){
            if(b&1) res=(res*a)%mod;
            b>>=1;
            a=(a*a)%mod;
        }
        return res%mod;
    }
    
    signed main(){
        scanf("%lld%lld%lld",&h,&w,&n);
        for(int i=1;i<=n;i++)
            scanf("%lld%lld",&a[i].x,&a[i].y);
            a[n+1].x=h,a[n+1].y=w;
            sort(a+1,a+n+2,cmp);
            for(int i=0;i<=N-10;i++) p[i]=(i==0)?1:((p[i-1]*i)%mod),inv[i]=fpow(p[i],mod-2);
            for(int i=1;i<=n+1;i++)
                f[i]=C(a[i].x-1,a[i].x+a[i].y-2);
                for(int i=1;i<=n+1;i++)
                    for(int j=1;j<=i-1;j++){
                        if(a[i].x<a[j].x||a[i].y<a[j].y) continue;
                        f[i]-=f[j]*C(a[i].x-a[j].x,a[i].x+a[i].y-a[j].x-a[j].y);
                        f[i]=(f[i]+mod)%mod;
                    }
                    printf("%lld
    ",(f[n+1]+mod)%mod);
                    return 0;
    }
    

    例题3

    POJ 1737 Connected Graph
    思路

    ​ 我们可以考虑求出N个点的无向图总数,减去N个点的不连通无向图数量,就是答案了。

    ​ N个点的无向图总数是 (2^{N*(N-1)/2}) 。含义是N个点的无向图最多有 (N*(n-1)/2) 条边,边有两种状态:有或无。

    ​ 接下来计算N个点的不连通数量。我们可以枚举标号为1的节点所在的联通块包含的节点数k,从2~N这N-1个节点中选出k-1个。显然有 (C_{N-1}^{~k-1}) 种选法。剩余N-k个节点构成任意无向图,有 (2^{(N-k)*(N-k-1)/2}) 种方法。

    ​ 设 (F[i]) 表示i个节点的无向连通图个数,则状态转移方程是:

    [F[i]=2^{i*(i-1)/2}-sumlimits_ {j=1}^{i-1}f[j]*C_{i-1}^{j-1}*2^{(i-j)*(i-j-1)/2} ]

    注意:因为没有模数,所以需要高精度!!!

    代码
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define re register
    #define ll long long
    
    using namespace std;
    struct Giao{
        int num[1001];
        Giao(){num[0]=1;}
        inline void clear(){
            memset(num,0,sizeof(num)),num[0]=1;
        }
        inline void read(){
            string s;cin>>s,num[0]=s.size();
            for(re int i=1;i<=num[0];i++) num[i]=s[num[0]-i];
        }
        inline void print(){
            for(re int i=num[0];i>0;i--) putchar(num[i]+48);
        }
        inline void operator = (string s){
            num[0]=s.size();
            for(re int i=1;i<=s.size();i++)
                num[i]=num[s.size()-i];
        }
        inline Giao operator + (Giao x){
            Giao y;y.clear();re int i;
            for(i=1;i<=num[0]||i<=x.num[0];i++){
                y.num[i]+=x.num[i]+num[i];
                if(y.num[i]>9) ++y.num[i+1],y.num[i]-=10;
            }y.num[0]=i;
            while(!y.num[y.num[0]]&&y.num[0]>1) --y.num[0];
            return y;
        }
        inline Giao operator - (Giao x){
            Giao y;y.clear();re int i;
            for(i=1;i<=num[0];i++){
                y.num[i]+=num[i]-x.num[i];
                if(y.num[i]<0) --y.num[i+1],y.num[i]+=10;
            }y.num[0]=i;
            while(!y.num[y.num[0]]&&y.num[0]>1) --y.num[0];
            return y;
        }
        inline Giao operator * (Giao x){
            Giao y;y.clear();re int i,j,k;
            for(i=1;i<=num[0];i++){
                k=0;
                for(j=1;j<=x.num[0];j++)
                    y.num[i+j-1]+=num[i]*x.num[j]+k,
                        k=y.num[i+j-1]/10,y.num[i+j-1]%=10;
                y.num[i+x.num[0]]+=k;
            }y.num[0]=i+j;
            while(!y.num[y.num[0]]&&y.num[0]>1) --y.num[0];
            return y;
        }
    }p2[2501],c[51][51],dp[51];//p2是2的次方 c是组合数 dp就是动态转移方程
    int n;
    
    int main(){
        p2[0]="1";
        for(int i=1;i<=2500;i++) p2[i]=p2[i-1]+p2[i-1];
        for(int i=0;i<=50;i++){
            c[i][0]="1";
            for(int j=1;j<=i;j++)
                c[i][j]=c[i-1][j]+c[i-1][j-1];
        }
        while(scanf("%d",&n),n){
            memset(dp,0,sizeof(dp)),dp[1]="1";
            for(int i=2;i<=n;i++){
                dp[i]=p2[i*(i-1)>>1];
                for(int j=1;j<i;j++)
                    dp[i]=dp[i]-dp[j]*c[i-1][j-1]*p2[(i-j)*(i-j-1)>>1];
            }dp[n].print(),putchar('
    ');
        }
        return 0;
    }
    

    例题4

    POJ 1037 A decorative fence
    思路

    [F[i,j,0]=sumlimits_{p=j}^{i-1}F[i-1,p,1]\F[i,j,1]=sumlimits_{p=1}^{j-1}F[i-1,p,0] ]

    代码
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    
    using namespace std;
    int t,n;
    ll m,f[21][21][2];
    
    inline void init(){
        f[1][1][0]=f[1][1][1]=1;
        for(int i=2;i<=20;i++)
            for(int j=1;j<=i;j++){
                for(int p=j;p<=i-1;p++) f[i][j][0]+=f[i-1][p][1];
                for(int p=1;p<=j-1;p++) f[i][j][1]+=f[i-1][p][0];
            }
    }
    
    int main(){
        init();
        scanf("%d",&t);
        while(t--){
            scanf("%d%lld",&n,&m);
            bool used[21];
            memset(used,0,sizeof(used));
            int last,k;
            //第1块木板,即可能处于高位,也可能处于高位,也可能处于低位,单独处理
            for(int j=1;j<=n;j++){
                if(f[n][j][1]>=m){
                    last=j,k=1;
                    break;
                }
                else m-=f[n][j][1];
                if(f[n][j][0]>=m){
                    last=j,k=0;
                    break;
                }
                else m-=f[n][j][0];
            }
            used[last]=1;
            printf("%d",last);
            //第2~n块木板,高低位置、合法的长度范围与上一块木板有关
            for(int i=2;i<=n;i++){
                k^=1;
                //真实长度为len,在剩余木板中排名为j
                int j=0;
                for(int len=1;len<=n;len++){
                    if(used[len]) continue;
                    j++;
                    if(k==0&&len<last||k==1&&len>last){
                        if(f[n-i+1][j][k]>=m){
                            last=len;
                            break;
                        }
                        else m-=f[n-i+1][j][k];
                    }
                }
                used[last]=1;
                printf(" %d",last);
            }
            puts("");
        }
        return 0;
    }
    
  • 相关阅读:
    知识图谱应急安全场景应用规划
    DataxWeb 设置运行错误
    Datax mysql 8.x elasticsearch 7.x 测试成功json样例
    testmysqltoelasticsearch76.json 未测试,仅参考
    testmysqltoelasticsearch75.json 未测试,仅参考
    testorcletoelasticsearch73.json 微测试,仅参考
    testmysqltoelasticsearch74.json 未测试,仅参考
    testmysqltoelasticsearch72.json 微测试,仅参考
    go可变参数
    Java 8 终于支持 Docker !
  • 原文地址:https://www.cnblogs.com/jasony/p/13624907.html
Copyright © 2020-2023  润新知