关于状压

动态规划——状态压缩

这里引入集合的概念,此集合可以理解为元素的状态集合
直接例题

EXAMPEL 1. 铺砖块

题目描述
现有n*m的一块地板,需要用1*2的砖块去铺满,中间不能留有空隙。问这样方案有多少种

输入
输入n,m(1<=n, m<=11)
有多组输入数据,以m=n=0结束
输出
输出铺砖块的方案数
样例输入
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
样例输出
1
0
1
2
3
5
144
51205

简单分析,每个砖块至多影响两行,至多影响两列
于是我们考虑分治(将问题分成形式相同,规模更小的问题)

问题的转化:

我们考虑下面的情况
1 1 1 1 … 1 1 1
1 1 1 1 … 1 1 1

1 1 1 1 … 1 1 1 (1表示已被覆盖,0表示还未被覆盖,左边表示前i-1行已完全铺满)
1 0 1 1 0 0 0 0 (第i行) 为了完全覆盖该层,我们可以在第2列放置一块竖的,5、6两列放置一块横的,7、8两列各放置一块竖的。
对于这种决策,我们得到
前i-1全部铺满后,第i的状态集合为 1 0 1 1 0 0 0 0 时
i+1行受其影响得到的其中一种状态集合 为 0 1 0 0 0 0 1 1
故而原问题等价于 前n行全部铺满后,第n+1的状态集合为0 0 0 0 0 0 0 0 的方案数

状态的定义:

f[i-1][sta] 表示前i-1行已经全部铺满,第i行的状态集合为sta的方案数
目标状态 f[n][0]
起始状态 f[0][0]=1

状态的转移:

f[i][change[j][1]]+=f[i-1][change[j][0]]

代码实现:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL f[15][100005];
LL change[100005][2];
LL n,m,num;
void dfs(LL t,LL from,LL to)
{
    if (t>m) return;
    if (t==m)
    {
        num++;
        change[num][0]=from;
        change[num][1]=to; 
    }
    dfs(t+1,(from<<1)+1,to<<1);
    dfs(t+1,from<<1,(to<<1)+1);
    dfs(t+2,from<<2,to<<2);
}
int main()
{
    scanf("%lld%lld",&n,&m);
    while (not ((n==0) && (m==0) ))
    {   
        memset(f,0,sizeof(f));
        memset(change,0,sizeof(change));
        f[0][0]=1;
        num=0;
        dfs(0,0,0); 
        for (int i=1;i<=n;i++)
         for (int j=1;j<=num;j++)
          f[i][change[j][1]]+=f[i-1][change[j][0]];
        printf("%lld\n",f[n][0]); 
        scanf("%lld%lld",&n,&m);
    }
}

EXAMPEL 2. 导游

题目描述
宁波市的中小学生们在镇海中学参加程序设计比赛之余,热情的主办方邀请同学们参观镇海中学内的各处景点,已知镇海中学内共有n处景点。现在有n位该校的学生志愿承担导游和讲解任务。每个学生志愿者对各个景点的熟悉程度是不同的,如何将n位导游分配至n处景点,使得总的熟悉程度最大呢?要求每个景点处都有一个学生导游。
输入
有若干行:
第一行只有一个正整数n,表示有n个景点和n个学生导游。
第二行至第n+1行共n行,每行有n个以空格分隔的正整数。第i+1行的第j个数k(1≤k≤1000),表示第i个学生导游对景点j的熟悉程度为k。
输出
只有一行,该行只有一个正整数,表示求得的熟悉程度之和的最大值。
样例输入
3
10 6 8
9 2 3
1 7 2
样例输出
24
提示
样例说明
第1个学生负责第3个景点,第2个学生负责第1个景点,第3个学生负责第2个景点时,熟悉程度总和为24,达到最大值。
数据限制
50%的数据,1≤n≤9;100%的数据,1≤n≤17。

问题的转换:

原问题等价于n个景点,每个景点恰有一个人的最大熟悉度

状态的定义:

f[sta]表示前Onenum(sta)(可以用lowbit处理)个人去状态集合sta的最大熟悉度

状态的转移(部分):

f[0]=0
for sta=1 to (1 shl n)-1
{
num=0
for i=0 to n-1
{if ((sta & (1 shl i))>0) num++
…}
}

代码实现:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int f[140000];
int a[20][20];
int dfs(int x){
  int ans=0;
  while (x>0){
    if (x%2==1) ans++;
    x>>=1; 
  }
  return ans;
}
int main(){
  int n;
  scanf("%d",&n);
  for (int i=1;i<=n;++i)
    for (int j=1;j<=n;++j)
      scanf("%d",&a[i][j]);
  f[0]=0;
  memset(f,0,sizeof(f));
  for (int i=1;i<=(1<<n)-1;++i){
    int num=dfs(i);
    for (int j=0;j<=n-1;++j)
    if ((i&(1<<j))>0)
      f[i]=max(f[i],f[i-(1<<j)]+a[num][j+1]);
  }     
  printf("%d\n",f[(1<<n)-1]);
  return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值