狀壓DP
咱都是用下標表示狀態。然而,如果一個狀態有20個下標,但只表示一個01值,難道要用20維數組嗎?
不成!
電腦裏數字都是二進制,即一個01串,就可以用一個數表示20位的狀態。這個狀態也可以是有20個元素的集合,所以《挑戰程序設計競賽》中叫它“針對集合的動態規劃”。
理論上,下標都是unsighed int 型,而unsighed int 型是32位的,也就是能表示32維的狀態。不過這樣佔內存很大,當32位都是1時,就會有
一般題目都是16位的。
對集合的一些操作
- 空集
∅ 0
- 單元素集 { i }
1<<i //從0開始標號
- 全集 U
(1<<n)-1 //有n個元素
- 從集合S中取元素 i
(s>>i)&1
- 並集
S∪T s|t
- 交集
S∩T 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的子集
//抄的p156 int 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。不過比較麻煩,其實內存還是可以接受的(
瞎搞的
洛谷 P2704 炮兵陣地
這一題不是很經典,但也與狀壓有關
他把一行壓成一個數!
他把一行壓成一個數!
他把一行壓成一個數!
然後,這有兩個騷操作,其一是移一位再與,判斷有無相鄰兩個1,同理移兩位,判隔一個的。自己與自己,就是橫行;自己與上面,就是豎列。其二是篩選可能的方案數。其實這類方法很常見的,就是重複使用的東西先預處理出來,隨時取用。
O(
//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;
}
打完已廢……