3.26总结(状态压缩)

本文详细介绍了状态压缩在动态规划中的应用,涉及连通性dp的BFS问题、Travelling问题的三进制状态压缩以及最短路径问题的优化,通过实际代码展示了如何利用状态压缩技术减少空间复杂度和提升效率。
摘要由CSDN通过智能技术生成

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;
}

  • 41
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值