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

    状压dp

    概述

    状压dp就是用二进制表示状态,并且对其子集进行归并的动态规划。它基于基本的位运算,常出现在NOIP和省选中。

    状压有明显的数据范围暗示,一般在20左右

    三进制枚举

    有时我们需要先枚举一个子集,再枚举这个子集的一个子集,此时我们经常二进制枚举两层再判断是否包含。这样很直接,但不如三进制枚举快速。

    假设我们有集合(S),我们需要(Asubseteq S)(Bsubseteq A),此时(S)集合的元素有三种情况:只在(S)中;既在(S)中又在(A)中;同时在三个集合中。只有三种情况,于是我们可以三进制枚举。

    三进制枚举模式代码:

    for (int x = 0; x < (1 << n); ++x) {
        for (int y = x;; y = (y - 1) & x) {
            for (int i = 0; i < n; ++i) {
                if ((x >> i) & 1) {
                    // Do something
                }
                if ((y >> i) & 1) {
                    // Do something
                }
            }
            // Do something
            if (y == 0) break;
        }
    }
    

    代码中关键的语句是y = (y - 1) & x,-1保证状态不同且最接近原状态&保证是子集。

    例题:P1896 [SCOI2005]互不侵犯

    题意很简单,就是在(N imes N)的棋盘中放(K)个国王,使得国王之间不互吃。

    我们很容易想到状压dp,我们按行枚举,再枚举上一行的状态,再枚举这一行的状态,再枚举价格,转移即可。

    对于两层枚举状态,我们可以使用两层二进制枚举,也可以使用上面的三进制枚举。其中三进制枚举复杂度更低,也不需要检验正确性,其中(S)集合为所有状态,(A)集合为这一行不会被上一行影响的部分,(B)集合为当前行的状态。

    这应该是此题除了打表最快的做法了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int INF=1e9+7,MAXN=10,MAXP=1<<9,MAXK=85;
    int N,K,maxp,ilg[MAXP]/*illegal*/,cnt[MAXP];
    LL f[MAXN][MAXP][MAXK],ans;
    int main(){
    	scanf("%d%d",&N,&K);
    	maxp=1<<N;
    	for(int i=0;i<maxp;i++){
    		for(int j=0;j<N;j++){
    			if((i>>j)&1){
    				cnt[i]++;
    				if(((i>>j)&3)==3)
    					ilg[i]=1;
    			}
    		}
    		f[1][i][cnt[i]]=!ilg[i];
    	}
    	for(int i=2;i<=N;i++)/*line*/
    		for(int j=0;j<maxp;j++){/*last*/
    			if(ilg[j])
    				continue;
    			int no=((maxp-1)^(j|(j<<1)|(j>>1)))%maxp;
    			for(int k=no;;k=(k-1)&no){/*current*/
    				if(ilg[k])
    					continue;
    				if(k>=maxp)
    					return 0;
    				for(int l=0;l+cnt[k]<=K;l++)
    					f[i][k][l+cnt[k]]+=f[i-1][j][l];
    				if(!k)
    					break;
    			}
    		}
    	for(int i=0;i<maxp;i++)
    		ans+=f[N][i][K];
    	printf("%lld",ans);
    	return 0;
    }
    

    枚举排列

    对于一个排列,我们有时可以对于它的子集进行操作,此时可以使用状压dp。

    但有时我们面对的不是一个排列,而是有重复元素的数列,此时我们需要按照以下公式去重(用多重集合的排列)

    [frac{n!}{prod n_i!} ]

    其中(n_i)为每种元素的个数

    例题:P4163 [SCOI2007]排列
  • 相关阅读:
    Linux Apache Mysql PHP楷模设置配备摆设1
    谋划phpMyAdmin2.6以上版本数据乱码题目
    MYSQL到ORACLE法式迁徙的注意变乱
    关于MySQL中的mysqldump饬令的应用
    MySQL 5.0新特性教程 存储进程:第一讲
    Mysql 数据库的导入与导出
    Linux Apache Mysql PHP典范设置装备安排2
    MYSQL数据库初学者运用指南2
    图解MySQL数据库的铺排和操作3
    MySQL完成表中掏出随机数据
  • 原文地址:https://www.cnblogs.com/guoshaoyang/p/11296011.html
Copyright © 2011-2022 走看看