介绍
介绍
状态压缩就是使用某种方法,简明扼要地以最小代价来表示某种状态,通常是用一串01数字(二进制数)来表示各个点的状态。这就要求使用状态压缩的对象的点的状态必须只有两种,0 或 1;当然如果有三种状态用三进制来表示也未尝不可。
状态压缩DP:顾名思义,就是用状态压缩实现DP
状压DP主要分为集合式和棋盘式,本文只讨论集合式。
例子
有 n n n 件物品和一个容量为 V V V 的背包。放入第i件物品的占的空间为 w i w_i wi,得到的价值是 v i v_i vi;求解每种放法的背包价值;
我们考虑用状态压缩。
显然,对于
n
n
n 件物品,每件物品选或不选分别用
0
0
0 或
1
1
1 表示,那么
n
n
n 件物品的状态就可以用一个
n
n
n 位的二进制数确定。
举个例子:假设当前枚举的状态为
10100
10100
10100,那么空间和价值都可以从状态
00100
00100
00100 和
10000
10000
10000 推过去。
所以可以有此代码:
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
const int N=1<<13;
int f[N],f2[N],w[N],v[N],n;
signed main()
{
IOS;
n=10;//假设有10个物品。
for(int i=0;i<n;i++)
cin>>w[i]>>v[i];
for(int i=0;i<(1<<n);i++)
for(int j=0;j<n;j++)
{
if((1<<j)&i)
continue;
f[i|(1<<j)]=f[i]+w[j];
f2[i|(1<<j)]=f2[i]+v[j];
}
//那么每个f[i]和f2[i]就表示当选择情况为i(二进制转十进制后)时的空间和价值
return 0;
}
剖析
显然,一维DP仅仅是用二进制的表示方法压缩了状态。
那我们再来一道例题:
P4802 [CCO2015] 路短最
题目描述
本题译自 CCO 2015 Day1 T2「Artskjid」
你可以通过许多的算法找到从一个地方到另外一个地方的最短路径。人们在他们的车上安装 GPS 设备然后他们的手机告诉他们最快的到达目的地的方式。然而,当在假期时,Troy 喜欢慢慢旅游。他想找最长的到目的地的路径以便他可以在路途中看许多新的以及有趣的地方。
因此,一个有效的路径包含一个不同城市的序列
c
1
,
c
2
,
.
.
.
,
c
k
c_1,c_2,...,c_k
c1,c2,...,ck,并且对于每个
1
≤
i
<
k
1\le i<k
1≤i<k,有道路从
c
i
c_i
ci 通往
c
i
+
1
c_{i+1}
ci+1。
他不想重复访问任何城市,请帮他找出最长路径。
输入格式
第一行输入包括两个整数
n
,
m
n,m
n,m,分别表示城市总数和连接城市间的道路数,两城市间至多有一条道路。城市编号从
0
0
0 到
n
−
1
n-1
n−1,Troy 一开始在城市
0
0
0,城市
n
−
1
n-1
n−1 是他的目的地。
接下来
m
m
m 行每行三个整数
s
,
d
,
l
s,d,l
s,d,l,每个三元组表示这里有一条长为
l
l
l 的从城市
s
s
s 到城市
d
d
d 的路。每条路都是有向的,只能从
s
s
s 到
d
d
d,不能反向。保证有一条从城市
0
0
0 到
n
−
1
n-1
n−1 的路径。
输出格式
输出一个整数表示以城市 0 0 0 为起点,以 n − 1 n-1 n−1 为终点的最长路径长度,并且其中不重复访问城市,路径长度是所经过的道路长度之和。
样例 #1
样例输入 #1
3 3
0 2 5
0 1 4
1 2 3
样例输出 #1
7
提示
最短路为直接走城市
0
0
0 至城市
2
2
2 的道路,长度为
5
5
5 km。最长路为
0
0
0 至
1
1
1 至
2
2
2, 长度
4
+
3
=
7
4+3=7
4+3=7 km。
对于至少
30
%
30\%
30% 的数据,
n
≤
8
n\le 8
n≤8;
对于
100
%
100\%
100% 的数据,有
2
≤
n
≤
18
,
2\le n \le 18,
2≤n≤18,
1
≤
m
≤
n
2
−
n
,
1\le m \le n^2-n,
1≤m≤n2−n,
0
≤
s
,
d
≤
n
−
1
,
0\le s,d \le n-1,
0≤s,d≤n−1,
s
≠
d
,
s\neq d,
s=d,
1
≤
l
≤
10000
1\le l\le 10000
1≤l≤10000。
分析
设 f [ i ] [ j ] f[i][j] f[i][j] 表示当前走到了 j j j 好节点,走过点的状态为 i i i 时的路程。
易有转移方程:
f[i][j]=max(f[i][j],f[i-(1<<j)][k]+d[k][j]);
不过需要满足:
if((1<<j)&i)
if(k!=j&&((1<<k)&i)&&f[i-(1<<j)][k]!=-1)
if(d[k][j])
即:
- i i i 是有 j j j 这一位的,或者理解为状态 i i i 的第 j j j 位为1。
- 上一个节点 k k k 和 j j j 不能一样。
- i i i 是有 k k k 这一位的,或者理解为状态 i i i 的第 k k k 位为1。
- 上一步状态可用。
- k k k 和 j j j 之间有路连接。
所以代码就出来了。
Code
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
const int N=1<<19;
typedef double lf;
int n,m;
int d[21][21],f[N][20],x,y,z;
signed main()
{
IOS;
memset(f,-1,sizeof f);
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>x>>y>>z,d[x][y]=z;
f[1][0]=0;
for(int i=0;i<(1<<n);i++)
{
for(int j=0;j<n;j++)
{
if((1<<j)&i)
for(int k=0;k<n;k++)
if(k!=j&&((1<<k)&i)&&f[i-(1<<j)][k]!=-1)
{
if(d[k][j])
f[i][j]=max(f[i][j],f[i-(1<<j)][k]+d[k][j]);
}
}
}
int ans=0;
for(int i=1;i<(1<<n);i++)
ans=max(ans,f[i][n-1]);
cout<<ans;
return 0;
}