状态压缩,就是用二进制来作DP的状态,用位运算来转移状态
二进制操作
左移
x<<y表示将二进制x的每一位向左移y位,在末位补0
例:
001011<<2=010110<<1=101100
x<<y=x*2^y
右移
x>>y表示将二进制x的每一位向右移y位,末位为1时舍去
例:
01010>>2=00101>>1=00010
由于会舍去末位1,等同于整除运算
x>>y=x/2^y
与运算
x&y表示对于x与y的每一位都进行与运算
x=10010
y=100=00110
x|y=00010
或运算
x|y表示对于x与y的每一位都进行或运算
x=10010
y=100=00100
x|y=10110非运算
~x表示对x的每一位取反
x=10010
~x=01101
异或运算
如果两位不同,则为1,否则为0
x=1001
y=111=0111
x^y=1110
较复杂的位运算常用操作
枚举n的子集:
for (int i=n;i;i=n&(i-1))
将x的第i位变成0:
x=x&(~(1<<(i-1)))
将x的第i位取出:
x=x&(1<<(i-1))
状态压缩例题
光棍组织
题目描述
MM 虽然一辈子只要一个,但是也得早点解决。于是,n 个光棍们自发组成了一个光棍组织 。
现在,光棍们打算分成几个小组,并且分头为 找 MM 事业做贡献。
对于这 n 个光棍的任意一个组合,都有一个被称为“和谐度”的东西,现在,他们想知道, 如何分组
可以使和谐度总和最大。 每个光棍都必须属于某个分组,可以一个人一组。
输入格式
第 1 行为 n,接下来 2^n-1 行,按照 2 进制给出每个分组的和谐度。
输出格式
仅 1 行,为最大和谐度和。
样例数据
input
3
41
12
57
94
89
23
12
output
151
数据规模与约定
数据范围 对于 30%数据,n<=5;
对于 100%数据 n<=16,1<=每个组的和谐度<=1000000,输入均为整数。
解析
inline void work()
{
for (int i=1;i<=n;i++)//注意程序中的n已经赋为(1<<n)-1
dp[i]=read();//对dp数组的初始化,
for (int i=1;i<=n;i++)//枚举i
for (int j=i&(i-1);j;j=i&(j-1))//枚举i的子集
dp[i]=max(dp[i],dp[j]+dp[i-j]);//j与i-j两个i的子集可以合并为i
printf("%d\n",dp[n]);//输出
}
种花小游戏
题目描述
植物大战僵尸这款游戏中,还有个特别有意思的赚钱方式——种花(能长金币的花)。 种出来的金币需要玩家点击才能得到,或者,玩家可以购买一只蜗牛来帮助捡金币。然而,蜗牛爬得慢是众所周知的。
所以,场上有若干金币时,蜗牛总是喜欢以最少的行程来捡走所有的金币。 现在告诉你场上n个金币所在位置的坐标,以及蜗牛所在位置,让你求出蜗牛捡走所有金币的最小行程
输入格式
第一行一个正整数n,表示金币数量 之后n行,每行两个非负整数x、y,分别表示金币所在位置坐标 最后一行两个正整数x、y表示蜗牛起始位置。
输出格式
一个实数(保留2位小数),表示最短行程
样例数据
input
4
0 1
1 1
1 0
2 2
0 0
output
4.83
数据规模与约定
对于20%的数据
n=3对于70%的数据
n<=8对于100%的数据
n<=16 x、y<=10000
解析
可以先设:dp[i][j]为在i(一个01串,每一位是1则表示已经捡取,0表示未捡取)并且当前在第j个地点时的最短路径。
先预处理出任两点i,j之间的距离dis[i][j]=dis[j][i],则得到:
dp[i][j]=f[i-(1<<(j-1))][k]+dis[j][k]
就可以写出代码了:
inline void work()
{
for (int i=1;i<=m;i++)//m=1<<n-1
for (int j=1;j<=n;j++)
for (int k=1;k<=n;k++)
if ((j^k || ~i-(1<<j-1)) && ((i>>j-1)&1) && ((i>>k-1)&1))
dp[i][j]=min(dp[i][j],dp[i-(1<<j-1)][k]+dis[j][k]);
double ans=2e9;
for (int i=1;i<=n;i++)
ans=min(ans,dp[m][i]);
printf("%.2lf\n",ans);
}