zoukankan      html  css  js  c++  java
  • 状压DP

    状态压缩是设计dp状态的一种方式。

    当普通的dp状态维数很多(或者说维数与输入数据有关),但每一维总量很少时,可以将多维状态压缩为一维来记录。

    这种题目最明显的特征就是:都存在某一给定信息的范围非常小(在20以内),而我们在dp中所谓压缩的就是这一信息。

    (或者是在做题过程中分析出了某一信息种类数很少)

    我们来看个例子。

     

    经典题

    给出一个n*m的棋盘,要放上一些棋子,要求不能有任意两个棋子相邻。求方案数。

    n<=100;

    m<=8。

     

    如果m固定的话可以设f[i][0/1][0/1]...[0/1]表示每一行每一列放不放

    如果不是固定的呢?

    我们发现后面的多个0/1可以看成一个二进制数

    那我们不就可以用数字代替后面的维数吗?
    f[i][s]->f[i+1][s’](s&s’==0)

     

    你会发现这样记录很暴力,状态数是与m相关的指数级的,但同时也就是因为m小我们就确实可以这么做。

     

    其实本质就是很暴力的记录状态,只不过利用了题目本身的特殊条件(这一维很小),使得我们并不会因此复杂度过高。

    同时也就是说,如果题目本身没有这样一个较小的信息,就不能应用状态压缩。

    状态压缩dp肯定是有一维是指数级的,这正是状态压缩的特点。

    来看一道题:

    P1896 [SCOI2005]互不侵犯

                      

      

     

    这个题可以状压DP的很明显的标志就是数据范围

    我们设f[i][j][k]表示当前在第i行,这一行及之前总共放了j个国王,当前的状态是k

    那么我们只要枚举行,然后再枚举状态转移就可以了

    怎么判断互不侵犯?

    用位运算就可以了

    注意最后答案不能光统计最后一行,因为不一定在最后一行才用完所有的国王

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,king;
    ll f[10][100][2000];
    int s[2000],num[2000];
    int cnt;
    ll ans;
    
    inline void pre()
    {
        int tot=(1<<n)-1;
        for(int i=0;i<=tot;i++)
        {
            if(!((i<<1)&i))
            {
                s[++cnt]=i;
                int k=i;
                while(k)
                {
                    num[cnt]+=k%2;
                    k/=2;
                }
            }
        }
    }
    
    inline void dp()
    {
        for(int i=1;i<=cnt;i++)
        {
            if(num[i]<=king) f[1][num[i]][i]=1;
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=1;j<=cnt;j++)
            {
                for(int k=1;k<=cnt;k++)
                {
                    if((!(s[k]&s[j]))&&(!((s[k]<<1)&s[j]))&&(!(s[k]&(s[j]<<1))))
                    {
                        for(int use=1;use<=king-num[j];use++)
                        {
                            f[i][num[j]+use][j]+=f[i-1][use][k];
                        }
                    }
                }
            }
        }
    }
    
    int main()
    {
        scanf("%d%d",&n,&king);
        pre();
        dp();
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=cnt;j++)
            {
                ans+=f[i][king][j];
            }
        }
        cout<<ans;
    }
  • 相关阅读:
    elasticSearch 查询 bool
    elasticSearch 查询 term
    elasticSearch 查询 match
    python re
    vue day1
    mysql 报错记录
    node.js vue.js 安装
    mysql 插入数据 ,存在跳过
    打印乘法口诀表
    初步使用分支、循环判断数字大小
  • 原文地址:https://www.cnblogs.com/lcezych/p/11460484.html
Copyright © 2011-2022 走看看