状压DP

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

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

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

(或者是在作题过程当中分析出了某一信息种类数不多)3d

咱们来看个例子。code

 

经典题blog

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

n<=100;class

m<=8。二进制

 

若是m固定的话能够设f[i][0/1][0/1]...[0/1]表示每一行每一列放不放im

若是不是固定的呢?

咱们发现后面的多个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;
}
相关文章
相关标签/搜索