状压dp也是以前的坑了…填坑ing…
在状压dp中位运算的常见应用
1.判断一个数字x二进制下第i位是不是等于1。
方法:x&(1<<(i-1))
将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。
2.将一个数字x二进制下第i位更改成1。
方法:x=x|(1<<(i-1))
证明方法与1类似,此处不再重复证明。
3.把一个数字二进制下最靠右的第一个1去掉。
方法:x=x&(x-1)
4.把一个数字二进制下第i位置0
方法:x=x&~(1<<(i-1))
功能 | 示例 | 位运算 | |
---|---|---|---|
去掉最后一位 | (101101->10110) | x>>1 | |
在最后加一个0 | (101101->1011010) | x<<1 | |
在最后加一个1 | (101101->1011011) | x<<1+1 | |
把最后一位变成1 | (101100->101101) | x\ | 1 |
把最后一位变成0 | (101101->101100) | x\ | 1-1 |
最后一位取反 | (101101->101100) | x^1 | |
把右数第k位变成1 | (101001->101101,k=3) | x\ | (1<<(k-1)) |
把右数第k位变成0 | (101101->101001,k=3) | x&~(1<<(k-1)) | |
右数第k位取反 | (101001->101101,k=3) | x^(1<<(k-1)) | |
取末三位 | (1101101->101) | x&7 | |
取末k位 | (1101101->1101,k=5) | x&((1<<k)-1) | |
取右数第k位 | (1101101->1,k=4) | x>>(k-1)&1 | |
把末k位变成1 | (101001->101111,k=4) | x\ | (1<<k-1) |
末k位取反 | (101001->100110,k=4) | x^ (1<<k-2) | |
把右边连续的1变成0 | (100101111->100100000) | x&(x+1) | |
把右起第一个0变成1 | (100101111->100111111) | x\ | (x+1) |
把右边连续的0变成1 | (11011000->11011111) | x\ | (x-1) |
取右边连续的1 | (100101111->1111) | (x^(x+1))>>1 | |
去掉右起第一个1的左边 | (100101000->1000) | x&(x^(x-1)) |
适用情况
看到N和M范围差别特别大,而且N的范围特别小时(也有可能都很小,在20以内时),首先就应该想到正确算法与这两个范围有关!
因此进一步可以考虑使用状压dp求解!!
poj 3254
题意:有n*m大的一个地方,1表示土地肥沃可以种植物,0表示不能种植物,问:在不许有两个植物相邻的情况下,有多少种放置的方法。
思路:参考博客:http://blog.csdn.net/mengxiang000000/article/details/51075506
1⃣️预处理出来每一行不相邻的情况。
int num=0;
void init()//去除所有相邻的情况
{
for(int i=0;i<(1<<12);i++)
if((i&(i<<1))==0)//i左移一位,相邻就相当于左移之后的数字和原数字相与都是0
fit[++num]=i;
}
2⃣️保存土地的肥沃情况。
int m,n;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
int x;
scanf("%d",&x);
mp[i]=(mp[i]<<1)+x;
}
3⃣️动态规划。
dp(i)(j):推到第i行,第i行为j状态时的方法数。
dp(i)(j)=sum{dp(i-1)(k),(1<=k<=num&&k要符合土地肥沃条件和不相邻条件)}
边界条件就是第一行符合土地肥沃条件的为1,不符合的为0。
符合土地肥沃条件:
该状态和土地肥沃情况或运算之后为本来的土地肥沃情况。
符合不相邻条件:
该状态和上一行状态与运算之后为0。
for(int j=1;j<=num;j++)
{
if(fit[j]>=(1<<n))break;
if((mp[1]|fit[j])==mp[1])dp[1][j]=1;
else dp[1][j]=0;
}
for(int i=2;i<=m;i++)
for(int j=1;j<=num;j++)
{
if(fit[j]>=(1<<n))break;
if((mp[i]|fit[j])==mp[i])//在i行出现j状态是否符合土地条件
{
for(int k=1;k<=num;k++)
{
if((fit[k]&fit[j])==0)//是否符合上下行不能相邻条件
{
dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;
}
}
}
}
4⃣️把最后一行的所有方法数字相加即可。
完整代码:
int fit[500];//每行中不相邻的情况数
int mp[15];//保存每一行可否放牛情况
int dp[15][500];//第i行为j状态时的情况数
int num=0;
void init()//去除所有相邻的情况
{
for(int i=0;i<(1<<12);i++)
if((i&(i<<1))==0)//i左移一位
fit[++num]=i;
//printf("%d\n",num);
}
int main()
{
init();
int m,n;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
int x;
scanf("%d",&x);
mp[i]=(mp[i]<<1)+x;
}
for(int j=1;j<=num;j++)
{
if(fit[j]>=(1<<n))break;
if((mp[1]|fit[j])==mp[1])dp[1][j]=1;
else dp[1][j]=0;
}
for(int i=2;i<=m;i++)
for(int j=1;j<=num;j++)
{
if(fit[j]>=(1<<n))break;
if((mp[i]|fit[j])==mp[i])//在i行出现j状态是否符合土地条件
{
for(int k=1;k<=num;k++)
{
if((fit[k]&fit[j])==0)//是否符合上下行不能相邻条件
{
dp[i][j]=(dp[i][j]+dp[i-1][k])%MOD;
}
}
}
//printf("row=%d state=%d : %d\n",i,fit[j],dp[i][j]);
}
int ans=0;
for(int i=1;i<=num;i++)
ans=(ans+dp[m][i])%MOD;
printf("%d\n",ans);
return 0;
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!