状压DP

二进制

状压在很多时候都是通过二进制表示集合状态,然后状态转移。
比如3头牛ABC,
取A(001),取B(010),取C(100)
取AB(011),取BC(110),取AC(101)
取ABC(111)。

位运算操作

1、将a的第k位修改为1:a |= 1《《k;
2、将a的第k位修改为0:a &= ~(1《《k);
3、取第k位:a>>k & 1;
4.a,b两个集合合并,c=a|b.
5.用于枚举子集:for ( x = S; x ; x = S & ( x - 1 ) ) ;!!!

二进制例题

题面

光棍组织

统计
描述
提交
自定义测试
题目描述
MM 虽然一辈子只要一个,但是也得早点解决。于是,n 个光棍们自发组成了一个光棍组织 (ruffian organization,By Wind 乱译)。现在,光棍们打算分成几个小组,并且分头为 找 MM 事

业做贡献(For example:searching,hunting……By Wind 乱译)。 对于这 n 个光棍的任意一个组合,都有一个被称为“和谐度”的东西,现在,他们想知道, 如何分组

可以使和谐度总和最大。 每个光棍都必须属于某个分组,可以一个人一组。

输入格式
第 1 行为 n,接下来 2^n-1 行,按照 2 进制给出每个分组的和谐度。

(比如接下来第 5 行,也就是总共第六行,2 进制为 00000101,则表示第 1 个人和第 3 个人 这个分

组的和谐度,第 31 行则为 1~5 在一起的和谐度)

输出格式
仅 1 行,为最大和谐度和。

样例数据
input

3
41
12
57
94
89
23
12
output

151

解析

二进制表示状态,枚举各种子集分布情况。
然后求出最大值。

代码

#include<bits/stdc++.h>
using namespace std;
int x,n;
int dp[100010]={};
int main()
{
    scanf("%d",&n);
    for(int i=1;i<1<<n;++i)
      scanf("%d",&dp[i]);
    for(int i=1;i<1<<n;++i)
      for(int j=i&(i-1);j;j=i&(j-1))
        dp[i]=max(dp[i],dp[i-j]+dp[j]);
    printf("%d",dp[(1<<n)-1]);
    return 0;
}

状压

通过二进制表示状态后,就可以通过不同阶段的状态进行转移了。

例题

种花小游戏

统计
描述
提交
自定义测试
题目描述
植物大战僵尸这款游戏中,还有个特别有意思的赚钱方式——种花(能长金币的花)。 种出来的金币需要玩家点击才能得到,或者,玩家可以购买一只蜗牛来帮助捡金币。然而,蜗牛爬得

慢是众所周知的。所以,场上有若干金币时,蜗牛总是喜欢以最少的行程来捡走所有的金币。 现在告诉你场上n个金币所在位置的坐标,以及蜗牛所在位置,让你求出蜗牛捡走所有金币的最小行程

输入格式
第一行一个正整数n,表示金币数量 之后n行,每行两个非负整数x、y,分别表示金币所在位置坐标 最后一行两个正整数x、y表示蜗牛起始位置。

输出格式
一个实数(保留2位小数),表示最短行程

样例数据
input

4
0 1
1 1
1 0
2 2
0 0
output

4.83
说明:(0,0)?(1,0)?(0,1)?(1,1)?(2,2) 1 + 1.414 + 1 + 1.414 = 4.83

解析

dp[s][j] 。s表示二进制状态,通常是转为十进制的数。j表示最后经过的那个点。
易得j是属于s的。
而且在s-j中找到一个k,就可以通过dp[s-j][k]推到dp[s][j]。

代码

#include<bits/stdc++.h>
using namespace std;
int n;
int x[20],y[20]={},num[100000]={};
double cost[20][20]={},dp[100000][20]={};
int main()
{
    scanf("%d",&n);
    num[1]=1;
    for(int i=1;i<=n;++i)
      scanf("%d%d",&x[i],&y[i]),num[1<<i]=i+1;
    scanf("%d%d",&x[0],&y[0]);
    for(int i=0;i<=n;++i)
      for(int j=i+1;j<=n;++j)
        cost[j][i]=cost[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
    for(int i=1;i<1<<n;++i)
      for(int j=1;j<=n;++j)
        dp[i][j]=2000000000.00;
    for(int i=1;i<=n;++i)
      {
        dp[1<<(i-1)][i]=cost[0][i];
        for(int j=1;j<=n;++j)
          if (i!=j) 
            dp[(1<<(i-1))+(1<<(j-1))][j]=min(dp[(1<<(i-1))+(1<<(j-1))][j],dp[1<<(i-1)][i]+cost[i][j]);
      }      
    for(int i=1;i<1<<n;++i)
      for(int j=i;j;j-=j&-j)
        for(int k=i-(j&-j);k;k-=k&-k)
          dp[i][num[j&-j]]=min(dp[i][num[j&-j]],dp[i-(j&-j)][num[k&-k]]+cost[num[k&-k]][num[j&-j]]);
    double ans=2000000000.00;
    for(int i=1;i<=n;++i)
      ans=min(dp[(1<<n)-1][i],ans);
    printf("%.2lf",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值