压缩状态
把每种状态做离散化标记.
比如一个灯位于(i,j),灯亮的话light[ i ][ j ]=1;不亮就是0. 这个东西选是1不选是0. 这个点走过就是1没走过就是0等等……
所以我们把每种状态都可以哈希为一个十进制整数,这个整数根据每个点状态种类的数量不同可以对应转化为二进制、三进制、四进制等等来表示一个状态
比如二进制:1001,转化为十进制为9,那么状态[ 9 ]就代表着选四号位,不选三号位,不选二号位,选一号位这一种状态
再假设一个人只有三种状态,吃饭 0,睡觉 1,打东东 2,那么三进制:2101 转化为十进制64,那么状态[ 64 ]就代表着第四个人打东东,第三个人睡觉,第二个人吃饭,第一个人睡觉这样的状态
二进制操作
节选自传送门
’&’符号,x&y,会将两个十进制数在二进制下进行与运算(都1为1,其余为0) 然后返回其十进制下的值。例如 3(11)&2(10)=2(10)。
’|’符号,x|y,会将两个十进制数在二进制下进行或运算(都0为0,其余为1) 然后返回其十进制下的值。例如3(11)|2(10)=3(11)。
’^ ’符号,x ^ y,会将两个十进制数在二进制下进行异或运算(不同为1,其余 为0)然后返回其十进制下的值。例如3(11)^2(10)=1(01)。
’~ ’符号,~ x,按位取反。例如~101=010。
’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。 ’>>’符号,是右移操作,x>>1相当于给x/2,去掉x二进制下的最右一位.
1.判断一个数字x二进制下第i位是不是等于1。(最低第1位)
方法:if(((1<<(i−1))&x)>0) 将1左移i-1位,相当于制造了一个只有第i位 上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0, 说明x第i位上是1,反之则是0。
2.将一个数字x二进制下第i位更改成1。
方法:x=x|(1<<(i−1)) 证明方法与1类似。
3.将一个数字x二进制下第i位更改成0。
方法:x=x&~(1<<(i−1))
4.把一个数字二进制下最靠右的第一个1去掉。
方法:x=x&(x−1)
例题
我们先给出例题T140309 布阵
给定一个n*m的棋盘,相邻两格不能同时放棋子,求有多少种放法n,m<=12
输入
2 3
输出
17
理解一个“DP”所需要的定义
数组状态定义:dp[ i ][ p ] 表示 第i行 状态为p的方案数
状态转移:只与其上一行的状态有关 ,dp[ i ][ p ] = 对所有满足 p&q==0 的dp[ i-1 ][ q ] 求和
初始化:dp[ 0 ][ 0 ] = 1
最终答案 对于最后一行中符合p&(p>>1)的 dp[ n ][ p ] 求和
时间复杂度分析和优化
这样子时间复杂度很高,在做每一行的状态是2^n , 一共n行 , 转移时固定p遍历q,结果是n* 4 ^ n
这样子我们就对他进行优化,提前枚举并记录可以存在的状态
int num[inf];
int cnt=0;
int limit = 1<<m //m是列数
for(int i=0;i<limit;i++)
if((i&(i>>1))==0)
num[++cnt]=i;
据说num数组和斐波那契有关
上述全部是建立在状态不能1相邻的情况下,若题意改变则需要变换,同时,因为过高的复杂度,数据量很小可考虑状压DP
简略实现代码
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=cnt;j++)
{
int p=num[j];
for(int k=1;k<=cnt;k++)
{
int q=num[k];
if((p&q)==0)
dp[i][p]=(dp[i][p],dp[i-1][q])%MOD;
}
}
}
例题 AcWing 91.最短Hamilton路径 TSP
题目大意:给定一张完全图,求一条从0开始到n-1结束的,经过所有点恰好一次的最短路。1<=n<=20
状态就是已经走过了哪些点
理解一个“DP”所需要的定义
数组状态定义:dp[ i ][ p ] 表示当前状态p已经经过了,从0走到i的路径和最小值
状态转移:松弛(迪科斯彻算法)每条边
初始化:dp[ 0 ][ 1 ] = 0,其他为inf
最终答案 dp[ n-1 ][ (1<<n)-1 ]
简略实现代码
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>G[i][j];
dp[0][1]=0;
int limit=1<<n;
for(int i=1;i<limit;i++)//遍历状态
for(int j=0;j<n;j++) // 遍历起点
if((i>>j)&1)// 起点在状态内
for(int k=0;k<n;k++)// 遍历终点
if(((i>>k)&1)==0) // 终点不在状态内
dp[k][i|1<<k]=min(dp[k][i|1<<k],G[k][j]+dp[j][i]) // i|1<<k 意思是把状态的k位改成1
cout<<dp[n-1][limit-1]<<endl;