Formula 1 URAL - 1519 插头dp

本文介绍了一种使用插头动态规划(DP)结合Hash表和位运算优化的算法,用于解决复杂路径规划问题。通过分类讨论每个格子的情况,并利用滚动Hash表作为DP数组,实现快速状态更新和方案数量的存储。代码详细展示了如何初始化Hash表,更新状态,以及最终计算所有合法路径的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题解

使用插头dp求解 hash表+位运算加快存取速度 使用滚动hash表作为dp数组 hash表存储轮廓线上的"路径插头"对应的方案数量
分类讨论每个格子的情况 情况太多不想重新写了 具体看代码。。备注很详细
我的菜比代码没看懂的可以看某高中大佬blog

AC代码

#include <stdio.h>
#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const int MAXN = 20;
const int HASZ = 1e5 + 3;
int n, m, ex, ey; //最后一个有效格子
char g[MAXN][MAXN];

struct HashTable //利用hash表存储状态 遍历不需要find
{
	struct node
	{
		ll key, val;
		int nxt;
	}elem[HASZ]; //存储元素
	int head[HASZ], tot; //head存储每个位置的第一个元素编号
	void Init()
	{
		tot = 0;
		memset(head, -1, sizeof(head));
	}
	void Push(ll key, ll val)
	{
		int k = key % HASZ; //取模位置
		for (int i = head[k]; ~i; i = elem[i].nxt)
			if (elem[i].key == key) //key相等
			{
				elem[i].val += val; //已有+val
				return;
			}
		elem[tot].key = key, elem[tot].val = val, elem[tot].nxt = head[k]; //将原有接在后面
		head[k] = tot++; //head记录当前
	}
}d[2]; //0无 1左括号 2右括号 
inline int getbit(ll s, int p) //获取状态s第p位的值
{
	return (s >> (p - 1 << 1)) & 3;
}
inline void setbit(ll &s, int p, int v) //将状态s第p位设置为v
{
	s = s & ~(3 << (p - 1 << 1)) | (v << (p - 1 << 1));
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		scanf("%s", g[i] + 1);
		for (int j = 1; j <= m; j++)
			if (g[i][j] == '.')
				ex = i, ey = j;
	}
	if (!ex && !ey) //没有有效格子
		cout << 0 << endl, exit(0);
	d[1].Init();
	d[1].Push(0, 1); //最初什么都没有为1个答案
	int p = 0; //滚动数组 当前状态
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++) //逐格递推
		{
			d[p].Init(); //清空当前状态
			for (int k = 0; k < d[!p].tot; k++) //遍历上个状态
			{
				ll s = d[!p].elem[k].key, v = d[!p].elem[k].val; //状态 数量
				int l = getbit(s, j), u = getbit(s, j + 1); //获取左和上的插头状态
				if (g[i][j] == '*') //当前为墙
				{
					if (!l && !u) //左上都没插头才合法
						d[p].Push(s, v); //新的状态加继承原状态数量
				}
				else //不是墙
				{
					if (!l && !u) //都没有插头
					{
						if (g[i][j + 1] == '.' && g[i + 1][j] == '.') //下方和右侧都不是墙
							setbit(s, j, 1), setbit(s, j + 1, 2), d[p].Push(s, v); //创建两个插头并配对 ┌
					}
					else if (!l || !u) //有一个没有
					{
						if (g[i][j + 1] == '.') //右侧不是墙 向右延伸 ─└
							setbit(s, j, 0), setbit(s, j + 1, l + u), d[p].Push(s, v); //保持原有插头类型
						if (g[i + 1][j] == '.') //向下 ┐│
							setbit(s, j, l + u), setbit(s, j + 1, 0), d[p].Push(s, v);
					}
					else //都有插头
					{
						if (l == 1 && u == 2) //左侧是左括号 右侧是右括号则说明回路终点
						{
							if (i == ex && j == ey) //是最后一个格子才为合法状态
								setbit(s, j, 0), setbit(s, j + 1, 0), d[p].Push(s, v); //清除插头
						}
						else if (l == 2 && u == 1) //左侧是一个联通分量 右侧是一个联通分量
							setbit(s, j, 0), setbit(s, j + 1, 0), d[p].Push(s, v); //直接清除插头并合并
						else //两个同侧括号
						{
							int cnt = 0; //相同括号
							for (int e = j + (l == 1); e >= 1 && e <= m + 1; e += (l == 1 ? 1 : -1)) //同右向左 同左向右
							{
								int b = getbit(s, e);
								if (b == l) cnt++;
								else if (b) cnt--;
								if (!cnt) //找到配对的
								{
									setbit(s, e, l); //将括号设置为和当前同向
									setbit(s, j, 0), setbit(s, j + 1, 0); //并将当前两个括号清除
									d[p].Push(s, v);
									break;
								}
							}
						}
					}
				} 
			}
			p = !p; //切换数组
		}
		for (int j = 0; j < d[!p].tot; j++) //将状态全部左移2位为下一行做准备 ──┘-->┌──
			d[!p].elem[j].key <<= 2;
	}
	ll ans = 0;
	for (int i = 0; i < d[!p].tot; i++) //将最后一个状态的数量求和
		ans += d[!p].elem[i].val;
	cout << ans << endl;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值