首页 > 其他分享 >题解 P5507 机关

题解 P5507 机关

时间:2023-02-01 13:23:42浏览次数:74  
标签:状态 耗散 sta P5507 题解 后继 int 估价 机关

传送门

毕设老师让我做 MAPF,由于学了好多 A* 算法的变形,就过来做一做了。

这题用的是 EPEA*(Enhanced Partial Expansion A*)算法


【分析】

显然搜索能过,但是状态空间太大了。一眼可 A*。

考虑所有机关的状态为 \(s_i\) 时:

若没有连锁反应,最少步数为 \(\displaystyle h_1(\boldsymbol s)=\sum_i((5-s_i)\bmod 4)\) 。

现在由于有连锁反应,我们设计估价函数的时候需要保证估价函数的结果不大于真实步数;

为此,我们考虑最优情况下,每次转动一个机关会导致另一个机关跟着转动;也就是说,这种情况下等价于没有连锁反应的情况下转两次。

因此估价函数可以设计为 \(\displaystyle h_2(\boldsymbol s)={1\over 2}h_1(\boldsymbol s)\) 。

当然,我们知道,估价函数在不超过真实值的情况下,估价函数的值越大,搜索效率越高。因此,对于奇数的 \(h_1(\boldsymbol s)\) ,我们直接取上取整,因此设计出估价函数:

\(\displaystyle h_2(\boldsymbol s)=\lceil{1\over 2}h_1(\boldsymbol s)\rceil\)


我们考虑一下一般的 A* 算法,在状态 \(S\) 下,通过维护当前耗散 \(g_S\) 和对于后续操作的估计耗散 \(h_S\) ,共同组合成总期望耗散 \(f_S=g_S+h_S\) 。

通过对待扩展状态的总期望耗散进行从小到大排序,优先拓展总期望耗散较小的状态。

EPEA* 则是在此基础上动态调整自身的后续估价耗散,从而达到动态调整总期望耗散的结果。

在拓展一个状态 \(S\) 时,EPEA* 对于其所有后继状态 \(\{T_i\}\) ,EPEA* 只拓展满足条件 \(f_S=f_{T_i}\) 的后继状态。

由于 A* 中,估价函数的结果是不超过真实值的,因此不可能存在 \(f_S>f_{T_i}\) 的情况。

而对于其他总期望耗散更大的后继状态,可能也能产生对答案的贡献。为此,我们修正 \(h_S\) 使得 \(f_S\) 更新为未选择的后继状态中,\(f_{T_i}\) 最小的结果;之后,将修正后的当前节点重新扔进待拓展的队列中。


如对于搜索到的某个状态 \(S\) ,其当前耗散 \(g_S=5\) 而根据估价函数产生了初始的后续估价耗散 \(h_S=4\) ,于是 \(f_S=5+4=9\) 。

假设它存在 \(3\) 个后续状态 \(T_1, T_2, T_3\) ,他们根据估价函数产生后续估价耗散 \(h_{T_1}=3, h_{T_2}=4, h_{T_3}=5\) ;那么,由于他们的当前耗散均为 \(6\) ,因此有 \(f_{T_1}=9, f_{T_2}=10, f_{T_3}=11\) 。

这一次的拓展中,我们仅拓展了 \(T_1\) 状态。而对于没拓展的后继状态中,最小的结果为 \(f_{T_2}=10\) ;因此在拓展后,我们修正 \(h_S=5\) 使得 \(f_S=10\) 。

当然,在具体的实现中,我们不必修正 \(h_S\) 的结果,直接修改 \(f_S\) 的结果也是等价的。


可以发现,EPEA* 适用于遍历后继状态较快,且评估后继状态的估价函数较快的问题。

对于这题而言,由于所有状态的后继状态都最多为 \(12\) 种,而估价函数对于每个状态可以在 \(O(1)\) 的时间内算出结果;恰好适用于这题。

最后,关于答案的维护,我们直接维护 \(fr_S, pace_S\) 数组分别表示状态 \(S\) 是从状态 \(fr_S\) 沿边 \(pace_S\) 转移而来的。

当然,为了避免搜索重复状态,可以再维护数组 \(vis_S\) 表示是否已经访问过了状态 \(S\) 。

因为 EPEA* 算法中保证了对每个状态,优先拓展总期望耗散和自身一致的后继。故当一个后继状态一旦被拓展到,后续的拓展方案中,必然不存在总期望耗散更优的拓展方案,可以直接记录 \(fr_S,pace_S,vis_S\) 结果。

论文里描述该算法在 MAPF 中对于状态空间的数量产生了 dramatically reduce 。但在算法竞赛题中,实测好像没那么强,甚至会更慢。

就当作作为一个拓展吧。


【代码】

为了实现方便,代码中将机关的状态 \(1,2,3,4\) 分别映射成了 \(0,1,2,3\) ,将机关 \(1\)~\(12\) 分别映射成了 \(0\)~\(11\) 。

#include <bits/stdc++.h>
using namespace std;
int a[12][4];
inline int h(int sta) {
	//Heuristic Function
	/*
	  12 * 2 = 24
	  24 / 4 = 6
	*/
	int s1 = (0x444444 - (sta & 0x333333)) & 0x333333;
	int s2 = (0x1111110 - (sta & 0xcccccc)) & 0xcccccc;
	int tmp = __builtin_popcount((s1 | s2) & 0x555555);
	int pace = __builtin_popcount(s1 | s2) * 2 - tmp;
	return pace + 1 >> 1;
}
inline int move(int sta, int id) {
	//Move the id-th mechanism while the state is sta.
	int single_sta = ((sta >> id + id)&3);
	int chain = a[id][single_sta];
	int chain_sta = ((sta >> chain + chain)&3);
	
	single_sta = (single_sta + 1)&3;
	chain_sta = (chain_sta + 1)&3;
	int msk = ((3 << id + id) | (3 << chain + chain));
	return sta&(~msk) | (single_sta << id + id) | (chain_sta << chain + chain);
}

struct node {
	int sta;
	int gv, hv, fv;
	inline node(int sta_=0, int gv_=0): sta(sta_), gv(gv_) {
		hv=h(sta_);
		fv=gv+hv;
	}
	inline friend bool operator < (const node &a, const node &b) {
		return a.fv > b.fv;
	}
};
priority_queue<node> pq;
int fr[1<<24], pace[1<<24];
bool vis[1<<24];
inline void EPEA_star(int sta) {
	while(!pq.empty()) pq.pop();
	pq.emplace(sta, 0);
	fr[sta]=-1;
	pace[sta]=-1;
	vis[sta]=1;
	
	while(!pq.empty()) {
		node now = pq.top(); pq.pop();
		if(now.sta == 0)//target state
			break;
		
		int nxtf=-1;
		for(int i=0; i<12; ++i) {
			int nxtsta = move(now.sta, i);
			if(vis[nxtsta])
				continue;
			int f = now.gv + 1 + h(nxtsta);
			if(f < now.fv)//expanded
				continue;
			else if(f == now.fv) {//expand
				pq.emplace(nxtsta, now.gv+1);
				fr[nxtsta] = now.sta;
				pace[nxtsta] = i;
				vis[nxtsta] = 1;
			}
			else if(nxtf == -1)//update the next f-value of this state
				nxtf = f;
			else
				nxtf = min(nxtf, f);
		}
		if(nxtf == -1)//expanded all the successors
			continue;
		now.fv = nxtf;
		pq.push(now);
	}
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int ini_sta=0;
	for(int i=0; i<12; ++i) {
		int s;
		cin>>s;
		ini_sta |= (s-1<<i+i);
		for(int j=0; j<4; ++j) {
			cin>>a[i][j];
			--a[i][j];
		}
	}
	EPEA_star(ini_sta);
	
	vector<int> v;
	for(int sta=0; ~fr[sta]; sta=fr[sta])
		v.push_back(pace[sta]);
	reverse(v.begin(), v.end());
	cout<<v.size()<<"\n";
	for(auto e : v) cout<<e+1<<" ";
	return 0;
}

标签:状态,耗散,sta,P5507,题解,后继,int,估价,机关
From: https://www.cnblogs.com/JustinRochester/p/17082211.html

相关文章

  • docker “no space left on device”问题解决
    在​​Linux​​环境上使用docker执行命令时遇到了“nospaceleftondevice”可能是存储镜像的路径磁盘满了1、先使用dockerinfo查看docker的信息dockerinfo可以看到do......
  • ptz2023题解/训练记录 #1 Petrozavodsk Winter Camp 2023 day1 JAGain in Petrozavods
    ProblemA.Agriculture签到题,没看,被队友切了ProblemB.BlocksandExpressions签到题,没看,被队友切了ProblemC.ChangingtheSequences首先,建图吧。然后,二分图最......
  • 题解 【[USACO23JAN] Lights Off G】
    problem给两个长为\(n\)的0/1字符串\(S,T\),进行如下操作\(cnt\)次:自行选定\(0\leqx<n\),使得\(T_x\)异或一。将\(S\)异或上\(T\)。将\(T\)的最后一位......
  • 题解 【[USACO23JAN] Find and Replace G】
    problem有一个字符串\(S\),初始时为\(\tt'a'\),现在进行\(n\)次操作,第\(i\)次操作形如:将\(S\)中的所有的字符\(ch_i\)替换为字符串\(T_i\)。然后输出\(S[l......
  • 【题解】favorite school
    标准程序1:#include<bits/stdc++.h>usingnamespacestd;intt,del,cha,flag[4],check[4];strings;unordered_map<char,int>pos;intslove(intx,inty,intz,int......
  • Acwing 周赛88 题解
    比赛链接A题题目描述给定一个整数\(x\),请你找到严格大于\(x\)且各位数字均不相同的最小整数\(y\)。\(1000\lex\le9000\)做法分析发现数据范围很小,那么我们可以直......
  • 【题解】USACO 2023 January Sliver
    因为Glod打寄了,就来写写Sliver的题解吧,现在应该比赛结束了吧。A.FindAndReplace题目分析:我们可以对给定的串建出一种关系,也就是如果在上面的字符串中是字符\(c_1......
  • CF1098D 题解
    题意传送门对于一个元素个数大于\(1\)的可重集,每次取出两个数\(x,y\)合并。若\(x\ley\le2x\),则称其为危险合并。重复上述操作至无法合并。给你一个初始为空的可......
  • 【题解】[LNOI2022] 盒
    题目分析:我们可以对每一条边单独计算贡献,这样会发现贡献很好算:\[ans=\sum_{i=0}^{n-1}w_i\sum_{j=0}^S|j-s_i|\binom{i+j-1}{i-1}\binom{S-j+n-i-1}{n......
  • 【题解】P5169 xtq的异或和
    再强没有xtq!!!1思路多项式的正确用法。首先根据P4151[WC2011]最大XOR和路径的神秘结论,这里只需要任意求出原图的一棵生成树,以及所有只包含一条非树边的简单环就可以维......