解题报告P1896 [SCOI2005]互不侵犯
Posted mudrobot
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解题报告P1896 [SCOI2005]互不侵犯相关的知识,希望对你有一定的参考价值。
我闷今天的目的就是通过这道题初步理解一下状态压缩类动态规划
首先我们先来介绍一下定义,所谓状态压缩类动态规划,顾名思义,这是以集合信息为状态的特殊的动态规划问题。主要有传统集合动态规划和基于连通性状态压缩的动态规划两种。
因为某些动态规划的需求信息量非常的大,并且我们为每一个信息开一维数组这样的做法是非常不现实的,所以说我们的状态压缩类动态规划也就应运而生了!
预备知识
位操作是一种非常快的基本运算:有左移、右移、与、或、非等运算。
左移:左移一位相当于某数乘2.
右移:右移一位相当于某数除以2.
与运算:按位进行“与”运算,两数同一位都为1时结果为1,否则为0,例如:101&110=100。
或运算:按位进行“或”运算,两数同一位都为0时结果为0,否则为1,例如:101|110=111。
非运算:按位取反。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;
}
以上是关于解题报告P1896 [SCOI2005]互不侵犯的主要内容,如果未能解决你的问题,请参考以下文章