【解題報告】P1896 [SCOI2005]互不侵犯
我悶今天的目的就是通過這道題初步理解一下狀態壓縮類動態規劃
首先我們先來介紹一下定義,所謂狀態壓縮類動態規劃,顧名思義,這是以集合資訊為狀態的特殊的動態規劃問題。主要有傳統集合動態規劃和基於連通性狀態壓縮的動態規劃兩種。
因為某些動態規劃的需求資訊量非常的大,並且我們為每一個資訊開一維陣列這樣的做法是非常不現實的,所以說我們的狀態壓縮類動態規劃也就應運而生了!
預備知識
位操作是一種非常快的基本運算:有左移、右移、與、或、非等運算。
左移:左移一位相當於某數乘2.
右移:右移一位相當於某數除以2.
與運算:按位進行“與”運算,兩數同一位都為1時結果為1,否則為0,例如:101&110=100。
或運算
非運算:按位取反。0變1,1變0,例如:~101=010。
於是學會了上面這些操作,我們就可以幹一些比較簡單的事情了。
-
判斷第i位是否為0,(S&(1<<i))==0,意思是將1左移i位與S進行與運算後,看結果是否為0.
-
將第i位設定為1,S|(1<<i),意思是將1左移i位與S進行或運算.
-
將第i位設定為0,S&~(1<<i),意思是S與第i位為0,其餘位為1的數進行與運算。
例如:S=1010101,i=5.
S&(1<<i):1010101&0100000=0000000.
S|(1<<i):1010101|0100000=1110101.
S&~(1<<i):1010101&1011111=1010101.
這裡在講程式碼前我覺得還是有必要在科普一個知識,就是關於判斷一個整數的二進位制數裡有幾個1。這裡我們需要用一個騷騷的操作。雖然我本人也沒有看懂。
int func(unsigned int n){
int count=0;
while(n>0){
n=n&(n-1);
count++;
}
return count;
}
這裡轉載一段:
比如輸入10,n-1=9,1010(10的二進位制)&1001(9的二進位制),得到一個1000(8的二進位制),n=8,count++=1
第二次迴圈,n-1=7,8&7=0,count++=2;跳出迴圈,返回count,也就是說10的二進位制裡面有2個1。。。。
壓行程式碼:
LL func(LL n){LL count=0;while(n>0){n=n&(n-1);count++;}return count;}
所以說大家應該也都懂了應該怎麼寫了!!!
程式碼如下:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL avai[2000],gs[2000],cnt=0,n,yong,f[10][2000][100],ans;
LL func(LL n){LL count=0;while(n>0){n=n&(n-1);count++;}return count;}
bool check(LL i){return (i&(i>>1))==0&&(i&(i<<1))==0;}
int main()
{
scanf("%lld%lld",&n,&yong);
for(LL i=0;i<(1<<n);++i)if(check(i))avai[++cnt]=i,gs[cnt]=func(i);//預處理所有的狀態,avai記錄的是狀態,而ge記錄的是當前狀態所用的國王的個數
for(LL i=1;i<=cnt;i++)f[1][i][gs[i]]=1;//第一層的所有狀態均是有1種情況的
for(LL i=2;i<=n;i++)
for(LL k=1;k<=cnt;k++)
for(LL j=1;j<=cnt;j++){//列舉i、j、k,j,k在迴圈中沒有特殊要求,反著寫也可以
if((avai[j]&avai[k])||((avai[j]<<1)&avai[k])||((avai[j]>>1)&avai[k]))continue;//排除不合法國王情況
for(LL s=yong;s>=gs[j];s--)f[i][j][s]+=f[i-1][k][s-gs[j]];
}//列舉s,計算f[i][j][s]
for(LL i=1;i<=cnt;i++)ans+=f[n][i][yong];
printf("%lld",ans);
return 0;
}