1. 程式人生 > >【題解】洛谷P1896 [SCOI2005] 互不侵犯(狀壓DP)

【題解】洛谷P1896 [SCOI2005] 互不侵犯(狀壓DP)

洛谷P1896:https://www.luogu.org/problemnew/show/P1896

前言

這是一道狀壓DP的經典題 

原來已經做過了 但是快要NOIP 複習一波

關於一些位運算的知識點參考:

https://blog.csdn.net/fox64194167/article/details/20692645

思路

看資料識算法系列

我們用f[i][j][k]來表示第i行為狀態j 並且前i行已經放了k個國王

對於狀態我們可以先預處理出來

因為每個格子有放和不放兩種選擇 

那麼我們可以想到轉化為二進位制來區分他們的狀態

如果有放為1 沒放為0

因此狀態最多可以達到2n種(每個格子都放)

所以我們預處理出所有的狀態 之後在進行DP詳細的判斷即可

程式碼

#include<iostream>
using namespace std;
#define ll long long
ll f[20][2000][155];
ll ans;
int num[2000],s[2000],n,k,cnt;//num為每種狀態可以防止的國王數
                              //s為狀態 
void pre()
{
    for(int i=0;i<(1<<n);i++)//列舉所有狀態 
    {
        
if(i&(i<<1)) continue;//如果衝突了 就跳過 //這裡可以看成同一行裡連著放了2個不滿足 int sum=0;//國王數 for(int j=0;j<n;j++)//統計此狀態放置的國王的個數 if(i&(1<<j)) sum++;//有放則為1 s[++cnt]=i;//新增狀態 num[cnt]=sum;//統計國王數 } } void dp() { f[0][1
][0]=1;//初始化 for(int i=1;i<=n;i++)//列舉行 for(int j=1;j<=cnt;j++)//列舉此行狀態 for(int sum=0;sum<=k;sum++)//列舉前i行的國王數 { if(sum>=num[j])//如果前i行的國王數大於這種狀態要放的國王數 //說明可以用這種狀態 { for(int t=1;t<=cnt;t++)//列舉第i-1行的狀態 { if(!(s[t]&s[j])&&!(s[t]&(s[j]<<1))&&!(s[t]&(s[j]>>1))) //無衝突 f[i][j][sum]+=f[i-1][t][sum-num[j]];//加上之前的方案 } } } for(int i=1;i<=cnt;i++) ans+=f[n][i][k];//ans為第n行已經放完所有國王的所有狀態的累計 cout<<ans; } int main() { cin>>n>>k; pre();//預處理 dp(); }