动态规划——状态压缩
这里引入集合的概念,此集合可以理解为元素的状态集合
直接例题
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;
}