好题分析---最短Hamilton路径+枚举优化

本文探讨了如何使用动态规划解决路径规划问题,通过枚举所有可能的路径并确保不重不漏,以找到最短距离。在给定的示例中,通过二进制表示路径并迭代更新状态,最终找到到达终点的最短路径。同时,文章还介绍了一种优化的枚举方法,用于字符串匹配,通过记录字符的下一个出现位置,提高查询效率。

++

 弄懂代码很容易,但是问题的关键是----为什么可以这么做??----对于状态的划分和转移,最关键的一点就是----不重不漏,对于确定的终点,我们应该要怎么设计方程呢?也就是说,要如何高效地枚举所有经过节点,并到达终点的情况。

假设:一共有七个点,用0,1,2,3,4,5,6来表示,那么先假设终点就是5,在这里我们再假设还没有走到5这个点,且走到的终点是4,那么有以下六种情况:
first: 0–>1–>2–>3–>4 距离:21
second: 0–>1–>3–>2–>4 距离:23
third: 0–>2–>1–>3–>4 距离:17
fourth: 0–>2–>3–>1–>4 距离:20
fifth: 0–>3–>1–>2–>4 距离:15
sixth: 0–>3–>2–>1–>4 距离:18

同理:假设还没有走到5这个点儿,且走到的终点是3,那么有一下六种情况:
first: 0–>1–>2–>4–>3 距离:27
second: 0–>1–>4–>2–>3 距离:22
third: 0–>2–>1–>4–>3 距离:19
fourth: 0–>2–>4–>1–>3 距离:24
fifth: 0–>4–>1–>2–>3 距离:26
sixth: 0–>4–>2–>1–>3 距离:17

那么由于4-5,3-5的距离是个定值,就可以以此来确定到达5的最短距离。因此,我们不妨可以这样枚举------确定一个终点,枚举所有点到该终点的距离,并取最小值来更新答案。

 .DP分析:
用二进制来表示要走的所以情况的路径,这里用i来代替
例如走0,1,2,4这三个点,则表示为:10111;
走0,2,3这三个点:1101;

QQ浏览器截图20200816153106.png

#include<iostream>
#include<cstring>
using namespace std;
const int M=1<<20;
int n;
int w[21][21];
int f[M][21];
int a,b;


int main()
{
    
    
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
          cin>>w[i][j];
        }
    }

    //对状态进行枚举,接着在每个状态下枚举所有终点的情况,对于终点来说,在对应的点,枚举可以由点k迁移而来的点。
    memset(f,0x3f3f3f,sizeof f);
    f[1][0]=0;
    for(int i=0;i<1<<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(i>>j&1)    //表示这种情况至少要包含j。
             {
                for(int k=0;k<n;k++)
                {
                    if((i-(1<<j))>>k&1)    //去掉j的所有路径是否包含k
                    {
                        f[i][j]=min(f[i-(1<<j)][k]+w[k][j],f[i][j]);
                    }
                }
             }
        }
    }
    
    
    cout<<f[(1<<n)-1][n-1];

    
    return 0;
}

(2) 

 本题采用优化枚举,用空间换时间,从后往前遍历,依次记录当前位置的字符对应的下一个(a——z)在什么位置,没有的话就置为-1,紧接着查询即可。


#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e6+5;
char a[maxn],b[maxn];
int t,last[30],nxt[maxn][30];
void init(){
        memset(last,-1,sizeof last);
    	int d=strlen(a+1);
        for(int i=d;i>=1;i--)
        {
           for(int j=1;j<=26;j++)
               nxt[i][j]=last[j];
             last[a[i]-'a'+1]=i;
        }
       
}
bool check(){

    int la=strlen(a+1);
    int lb=strlen(b+1);
    int i=last[b[1]-'a'+1];
    if(i==-1) return 0;
    for(int j=2;j<=lb;j++)
    {
        i=nxt[i][b[j]-'a'+1];
        if(i==-1) return 0;
    }
    
    return 1;
}
int main(){
	scanf("%s",a+1);
	init();
	scanf("%d",&t);
	while(t--){
		scanf("%s",b+1);
		if(check()){
			printf("Yes\n");
		}else{
			printf("No\n");
		}
	}
	return 0;
}

### Hamilton 路径算法与图论分析 Hamilton 路径在图论中是一个经典问,其目标是在一个加权图中找到一条从起点到终点的路径,该路径恰好经过每个顶点一次,并且路径的总权重小。该问属于 NP-Hard 问,因此对于大规模图,通常需要使用动态规划优化策略来求解。 #### 状态压缩动态规划方法 Hamilton 路径可以通过状态压缩动态规划来高效求解。状态压缩的核心思想是使用二进制数来表示已经访问过的顶点集合,从而减少状态存储的空间和计算复杂度。 定义状态 `dp[i][j]` 表示当前处于顶点 `j`,并且已经访问过的顶点集合由二进制数 `i` 表示时的路径长度。初始状态为 `dp[1][0] = 0`,表示从起点 `0` 出发,仅访问了自己。 状态转移方程为: ```python dp[i][j] = min(dp[i ^ (1 << j)][k] + weight[k][j]) for all k in i if k != j ``` 其中: - `i` 是一个二进制数,表示已访问的顶点集合。 - `j` 是当前所在的顶点。 - `weight[k][j]` 是顶点 `k` 到顶点 `j` 的边权值。 - `i ^ (1 << j)` 表示从集合 `i` 中移除顶点 `j`。 终答案是 `min(dp[(1 << n) - 1][j] + weight[j][n-1])`,其中 `n` 是图中顶点的数量,`j` 遍历所有可能的中间顶点。 #### 图论中的应用 在图论中, Hamilton 路径与旅行商问(TSP)密切相关,但不同之处在于 TSP 要求路径形成一个回路,而 Hamilton 路径只需要从起点到终点。该问在实际应用中广泛存在,例如电路设计、物流路径规划等领域。 #### 示例代码 以下是一个实现 Hamilton 路径的 Python 示例代码: ```python n = int(input()) weight = [list(map(int, input().split())) for _ in range(n)] # 初始化动态规划表 dp = [[float('inf')] * n for _ in range(1 << n)] dp[1][0] = 0 # 起点为顶点0 # 状态转移 for i in range(1 << n): for j in range(n): if (i >> j) & 1: for k in range(n): if (i ^ (1 << j)) >> k & 1: dp[i][j] = min(dp[i][j], dp[i ^ (1 << j)][k] + weight[k][j]) # 终结果 print(dp[(1 << n) - 1][n-1]) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值