1.状态压缩的定义
状态压缩就是使用某种方法,简明扼要的以最小代价来表示某种状态,通常使用01这种二进制数字来表示各个点的状态,这就要求使用状态压缩的点只有两种状态,比如棋盘上某个点是否放置了旗子,其只有两种状态,放了或者没放。当然了,如果有三种状态就是三进制,四种状态就是四进制,(一般来说最多就是三进制了)
状态压缩分为两类:1.连通性dp(棋盘式)
2.集合式dp(表示每一个元素是否在一个集合中)
本博客主要讲述第一种连通性dp。
2.使用条件
从状态压缩的特点来看,这个算法适用的题目符合以下的条件:
1.解法需要保存一定的状态数据(表示一种状态的一个数据值)
每个状态数据通常情况下是可以通过2进制来表示的。这就要求状态数据的每个单元只有两种状态,比如说棋盘上的格子,放棋子或者不放,或者是硬币的正反两面。这样用0或者1来表示状态数据的每个单元,而整个状态数据就是一个一串0和1组成的二进制数。
2.解法需要将状态数据实现为一个基本数据类型,比如int,long等等,即所谓的状态压缩。状态压缩的目的一方面是缩小了数据存储的空间,另一方面是在状态对比和状态整体处理时能够提高效率。这样就要求状态数据中的单元个数不能太大,比如用int来表示一个状态的时候,状态的单元个数不能超过32(32位的机器),所以题目一般都是至少有一维的数据范围
3.相关例题:
1.胜利大逃亡(续)
题解:这题一开始看到就感觉就要用到bfs,但是单纯的bfs是不够的,因为还要用到状态压缩,因为,你要确保是否在经过门的时候能够拿到钥匙,然后用二进制来存储即可,最多有十个要是,因此状态压缩数组最小要为2的10次方=1024,经历相应的钥匙就要获取钥匙,用位运算来存储钥匙的状态,然后就可以愉快的AC了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include<stdio.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
using namespace std;
int n,m,t;
char f[25][25];
bool vis[25][25][1029];//状态压缩数组,在i,j位置,k状态的情况下,是否能开门
int sx,sy;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
struct node
{
int x,y;//存储坐标
int state;//状态
int step;//到这步的步数
};
int bfs()
{
node all,b;
int i,j;
memset(vis,0,sizeof(vis));
queue<node>q;
q.push({sx,sy,0,0});
while(q.size())
{
all=q.front();
q.pop();
for(i=0;i<4;i++)
{
int xx=all.x+dx[i],yy=all.y+dy[i],tt=all.step+1,ss=all.state;
if(tt>=t)
continue;
if(xx<1||xx>n||yy<1||yy>m||f[xx][yy]=='*'||vis[xx][yy][ss]==true)
continue;
if(f[xx][yy]=='^')
{
return tt;
}
if(f[xx][yy]>='A'&&f[xx][yy]<='Z')//到了门,判断此时是否拿到钥匙可以开门
{
if((1<<(f[xx][yy]-'A'))&ss)
{
vis[xx][yy][ss]=true;
q.push({xx,yy,ss,tt});
}
continue;
}
if(f[xx][yy]>='a'&&f[xx][yy]<='z')//判断是否到了钥匙的位置
{
int state=ss|(1<<(f[xx][yy]-'a'));
if(!vis[xx][yy][state])
{
vis[xx][yy][state]=true;
q.push({xx,yy,state,tt});
}
continue;
}
if(!vis[xx][yy][ss])
{
vis[xx][yy][ss]=true;
q.push({xx,yy,ss,tt});
}
}
}
return -1;
}
int main()
{
while(cin>>n>>m>>t)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin >> f[i][j];
if(f[i][j]=='@')
{
sx=i;
sy=j;
}
}
}
printf("%d\n",bfs());
}
return 0;
}
2.Travelling
题解:状态压缩dp,但是这题最多可以走两次,因此我们要用三进制,0代表一次没走,1代表走了一次,2代表走了两次,假如到了2,还继续到这个点就可以直接跳过了,3的十次方为59000多,所以这里直接开到6w了,这样才可以存储所有状态,每到一个城市就要更新一下自身状态
用dp[i][j]去表示在第i的状态下,最后到达的带你是j,然后去枚举城市k,从j到k,然后利用dp数组去更新,然后就可以写出状态转移方程:dp[i+num[k]][k]=min(dp[i+num[k]][k],dp[i][j]+dis[j][k]);
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int num[11]={1};
int dis[11][11];
int dp[60000][11];
int n,m;
int x,y,w;
int ans,flag;
int main()
{
for(int i=1;i<=10;i++)
{
num[i]=num[i-1]*3;
}
while(cin>>n>>m)
{
ans=0x3f3f3f3f;
memset(dis,0x3f3f3f3f,sizeof(dis));
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&w);
x--,y--;
dis[x][y]=dis[y][x]=min(dis[x][y],w);
}
for(int i=1;i<=n;i++)
{
dp[num[i]][i]=0;
}
for(int i=1;i<=num[n];i++)
{
flag=0;
for(int j=0;j<n;j++)
{
if((i/num[j])%3==0)
{
flag=1;
continue;
}
for(int k=0;k<n;k++)
{
if((i/num[k])%3==2)
continue;
dp[i+num[k]][k]=min(dp[i+num[k]][k],dp[i][j]+dis[j][k]);
}
}
if(flag==0)
{
for(int j=0;j<n;j++)
{
ans=min(ans,dp[i][j]);
}
}
}
if(ans==0x3f3f3f3f)
ans=-1;
printf("%d\n",ans);
}
return 0;
}
4.附加题
最近在复习最短路径,所以额外多了一个最短路径题,
1.传送门
题解:最近再回顾最短路径算法,所以就把这题加上了,数据大小有100,我们可以遍历每两个点为传送门,然后进行floyd算法,这样乍一看,好像可行,但是时间复杂度为n的五次方,这数据直接炸了,所以要进行压缩,把时间复杂度归为n的四次方,这样数据就不会炸了
我们设立传送门,只需要看与这个传送门有关的数据即可
#include<bits/stdc++.h>
using namespace std;
int n,m;
int dis[105][105];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)
dis[i][j]=0;
else
dis[i][j]=0x3f3f3f3f;
}
}
for(int i=1;i<=m;i++)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
dis[x][y]=dis[y][x]=min(dis[x][y],c);
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(dis[i][j]>dis[i][k]+dis[k][j])
{
dis[i][j]=dis[i][k]+dis[k][j];
}
}
}
}
long long sum=2e9;
for(int k=1;k<=n;k++)
{
for(int l=k+1;l<=n;l++)
{
long long sum2=0;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
sum2+=min(dis[i][j],min(dis[i][k]+dis[l][j],dis[i][l]+dis[k][j]));
}
}
sum=min(sum2,sum);
}
}
printf("%lld",sum);
return 0;
}