状压DP

来源:互联网 发布:双系统扩大windows内存 编辑:程序博客网 时间:2024/05/21 06:23

状压DP

  • 状压DP
    • 对集合的一些操作
    • 几道题
      • 经典的
      • 瞎搞的

咱都是用下标表示状态。然而,如果一个状态有20个下标,但只表示一个01值,难道要用20维数组吗?

不成!

电脑里数字都是二进制,即一个01串,就可以用一个数表示20位的状态。这个状态也可以是有20个元素的集合,所以《挑战程序设计竞赛》中叫它“针对集合的动态规划”。

理论上,下标都是unsighed int 型,而unsighed int 型是32位的,也就是能表示32维的状态。不过这样占内存很大,当32位都是1时,就会有232=4294967296这么大的数组,早炸了。

一般题目都是16位的。

对集合的一些操作

  • 空集 0
  • 单元素集 { i } 1<<i //从0开始标号
  • 全集 U (1<<n)-1 //有n个元素
  • 从集合S中取元素 i (s>>i)&1
  • 并集 ST s|t
  • 交集 ST s&t
  • 从集合S中加入元素 i s|(1<<i)
  • 从集合S中去除元素 i s&~(1<<i)
  • 枚举全集U的子集 for(int s=1;s<(1<<n);s++)
  • 枚举集合S的子集 for(int S0=S;S0;S0=(S0-1)&S)
  • 枚举全集中大小为k的子集

    //抄的p156int comp=(1<<k)-1;while(comb<(1<<n)){ //comb就是子集,处理它int x=comb&-comb,y=comb+x;//lowbit(comb)?comb=((comb&~y)/(x>>1))|y;}//一脸懵逼……

(来自《挑战程序设计竞赛》p156和蓝书p70)

几道题

经典的

UVa 10817 Headmaster’s Headache
——题意来自蓝书p95——
某校有n个教师和m个求职者,已知每人的工资和能教的课程集合,要求支付最少的工资使得每门都至少有两名教师教学,在职教师必须招聘。

输入包含多组数据,每组第一行为3个整数s,m,n,科目个数1<=s<=8,在职教师数1<=m<=20,求职者数1<=n<=100;以下m行每行描述一个在职教师,其中第一个整数c (10 000<=c<=50 000)是工资,接下来的若干整数是他的科目列表;接下来n行是申请者,格式相同,结束标志是s=0。

UVa的输入比较坑……
——下面是我瞎想的——
s不是1~8么,而且是两名教师,就可以把它“翻倍”,低8位是第一个教本科目的,高8位是第二个教本科目的,有一个就把低8位对应的变1,有两个就把高8位对应的变1,然后就是01背包啦~
还可以改为低s位高s位(大数据相当于没改),当高位有了时把低位置0。不过比较麻烦,其实内存还是可以接受的((2161)20=1310720)。

瞎搞的

洛谷 P2704 炮兵阵地
这一题不是很经典,但也与状压有关

他把一行压成一个数!
他把一行压成一个数!
他把一行压成一个数!

然后,这有两个骚操作,其一是移一位再与,判断有无相邻两个1,同理移两位,判隔一个的。自己与自己,就是横行;自己与上面,就是竖列。其二是筛选可能的方案数。其实这类方法很常见的,就是重复使用的东西先预处理出来,随时取用。
O(n23m)(可能并不准)

P2704代码

//p2704#include<cstdio>#include<iostream>using namespace std;const int N=105;int mp[N],f[N][N][N],met[N],cnt[N],mcnt;//f[i][j][k]:第i行,第i行方案j,第i-1行方案k,存最大放炮个数//mp[i]:第i行的图;met[i]:可能的方案method,由地图决定//cnt[i]:这个方案能放多少炮;mcnt:可能的方案个数int main(){    int n,m;    char tmp[N];    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++){          scanf("%s",tmp);        for(int j=0;j<m;j++){            mp[i]<<=1;            if(tmp[j]=='H')                mp[i]|=1;            }        }//把一横行压在一位里,山丘==1    for(int i=0;i<(1<<m);i++)        if( (!(i&(i<<1)))&&(!(i&(i<<2))) ){            met[++mcnt]=i;            int tm=i;            while(tm){                cnt[mcnt]+=tm&1;                tm>>=1;                }            }//选取所有方案,符合两个1之间至少两个0    for(int i=1;i<=mcnt;i++){        if(met[i]&mp[1])continue;        f[1][i][1]=cnt[i];        }//第一行方案初始化,第三维其实随便    for(int i=1;i<=mcnt;i++){//第一行的方案        if(met[i]&mp[1])continue;//方案i与地图冲突    for(int j=1;j<=mcnt;j++){//第二行的方案        if(met[j]&mp[2]||//方案j与地图冲突           met[j]&met[i])continue;//方案j与方案i冲突        f[2][j][i]=cnt[j]+cnt[i];//方案i与j的数目总和        }    }    for(int i=3;i<=n;i++){//第i行        for(int j=1;j<=mcnt;j++){//第i行方案j            if(met[j]&mp[i])continue;//方案i与地图冲突            for(int k=1;k<=mcnt;k++){//第i-1行方案k                if(met[k]&mp[i-1]//方案k与地图冲突                 ||met[k]&met[j])continue;//方案k与方案j冲突                for(int l=1;l<=mcnt;l++){//第i-2行方案l                   if(met[l]&mp[i-2]//l与地图冲突                    ||met[l]&met[j]//l与j冲突                    ||met[l]&met[k])continue;//l与k冲突                                    f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+cnt[j]);                    }                }            }        }    int ans=0;    for(int i=1;i<=mcnt;i++)        for(int j=1;j<=mcnt;j++)            ans=max(ans,f[n][i][j]);    printf("%d",ans);    return 0;    }

打完已废……

原创粉丝点击