1. 程式人生 > >第一次接觸狀壓DP

第一次接觸狀壓DP

mark .com idt 規劃 p s 應用 滿足 usaco 不能

狀壓DP入門及理解

*(另類的暴力)*

一般狀態數不多的時候就會開數組,但是有的狀態並不好表示,於是,狀壓DP就產生了。

狀壓DP應該是分兩類的,一類是壓縮狀態,另一類是舍棄狀態。 我感覺初學狀壓DP難就難在二進制運算的應用,了解二進制運算符就顯得十分重要。

所以我們先看下表,如果有不會二進制簡單應用的請點擊https://blog.csdn.net/sinat_35121480/article/details/53510793(神犇請忽略...)

技術分享圖片技術分享圖片

下面就可以看題了:

對於狀壓壓縮,入門題[USACO06NOV]玉米田Corn Fields[SCOI2005]互不侵犯,思路基本上相同。

[USACO06NOV]玉米田Corn Fields https://www.lydsy.com/JudgeOnline/problem.php?id=1725

我自己做了這兩個題有兩點體會,如下:

第一點(第一題):我們可以通過循環預處理出所有的狀態,再在動態規劃的過程中判斷狀態是否可行,方案數累加即可。

這樣來,動態規劃的方程就很好推出來了(初學者可能有點困難)。

F[ I ][ j ] 表示前I行,第I行狀態為J的方案數,J存的是狀態,用二進制表示。

代碼如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include
<cmath> #include<algorithm> #include<bitset> using namespace std;//前 i 行 //當前行 狀態為j的方案數 int n,m,a[15][15],g[1<<12],f[15][1<<12],mod=1e8; int check(int x) { if(!((x<<1)&x)&&!((x>>1)&x)) return 1; else return 0; } int main(){ scanf(
"%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)//轉化成二進制 g[i]=(g[i]<<1)+a[i][j]; for(int i=0;i<(1<<m);i++) if(check(i)&&((i&g[1])==i))//後者表示在可以種植的地方選擇(全選/部分/不選)地方去種,有點子集的感覺。 f[1][i]=1; for(int i=2;i<=n;i++)//第i行 { for(int j=0;j<(1<<m);j++)//第i行狀態為J { if(check(j)&&((j&g[i])==j)) for(int k=0;k<(1<<m);k++)//第i-1行狀態為k { if(check(k)&&((k&g[i-1])==k)&&(!(k&j))) f[i][j]+=f[i-1][k],f[i][j]%=mod; } } } int ans=0; for(int i=0;i<(1<<m);i++) ans+=f[n][i],ans%=mod; printf("%d",ans%mod); return 0; }

[SCOI2005]互不侵犯

https://www.lydsy.com/JudgeOnline/problem.php?id=1087

第二點(第二題):我們可以通過DFS搜索出所有符合情況的方案記錄下來再進行動態規劃。

DFS過程中如果搜到行的盡頭,我們就保存狀態再返回,否則就進行下一步搜索。

搜索分兩種狀態:1.當前格子不放國王,搜索下一個格子 2.當前格子放國王,就得跳過下一個格子搜索。(代碼中有特別註釋)

我們先不要管在列方向上的約束,只管每一行的國王不能放在一起,每一列的國王不能放在一起那是下面要考慮的問題。

當所有滿足條件的行的狀態都搜索出來之後,就可以動態規了,中間剔除列上不符要求的狀態。

動態規劃方程也跟上題的差不多:

F[ I ][ J ] [ K ] 表示前 I 行 ,第 i 行狀態為J 總共選了K個國王的方案數。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long int
using namespace std;
ll n,t,top,sum[2000],zt[2000],f[20][1000][300],ans;//位置  狀態   選了幾個
void dfs(ll z,ll s,ll ci)
{
    if(ci>=n)
    {
        top++;
        zt[top]=z;
        sum[top]=s;
        return;
    }
    dfs(z,s,ci+1);//不選
    dfs(z+(1<<ci),s+1,ci+2);//
}
int main(){
    scanf("%lld%lld",&n,&t);
    dfs(0,0,0);
    for(ll i=1;i<=top;i++)f[1][i][sum[i]]=1;
    for(ll i=2;i<=n;i++)
      for(ll j=1;j<=top;j++)
        for(ll k=1;k<=top;k++)
        {
            if(zt[j]&zt[k])continue;
            if((zt[j]<<1)&zt[k])continue;
            if((zt[k]<<1)&zt[j])continue;
            for(ll q=sum[j];q<=t;q++){
                f[i][j][q]+=f[i-1][k][q-sum[j]];
                //前i行 第i行狀態為j  有q個國王的方案數。
            }
        }
    for(ll i=1;i<=top;i++)
    ans+=f[n][i][t];
    printf("%lld",ans);
    return 0;
}

嗯,這道題是右上角的大佬教我的。

我對於狀壓DP的理解剛剛入門,可能還有很多說的不妥當的地方希望各位神犇評論告知。

2018-07-28

19:17:14

第一次接觸狀壓DP