4.15 省选模拟赛 哈密顿回路 折半搜索 双指针

avatar
avatar

看一眼题目 显然点的状态之间合并也没用 所以状压dp就不行了。

观察点数 很少 但是边数很多 还是考虑爆搜点数。

容易获得 30分的阶乘搜索。

其实很容易发现这个可以使用折半搜索来优化。

不过考试的时候 想了一个非常麻烦的折半搜索 所以GG.

原来的思路:由于一定形成回路 那么 可以先2^n枚举其中的集合 然后爆搜出所有的路径。

对于另一半也同理 合并的时候采用二分处理。

不过考试的时候 由于睡了2h 没时间了 所以没细想。

现在想想当时2^n枚举其中集合是没必要的做法。

首先 我们搜一半再拼接肯定是比较优的。

考虑直接搜一半 考虑起点 因为是回路所以从1开始也行。搜到一半就停止 然后 放到vector里或者map里来进行两端对接。

考虑对接的时候 如果我们知道某个路径的终点 和起点 加上边权即可。

不过这样在对接的时候还得暴力枚举。

考虑直接在搜的时候 同一个起点开始也让他们同一个终点结束 这样只要中间的集合不同这样我们就可以直接对接起来。

对接建议使用sort+双指针这样常数比较小。

注意 搜前一半和后一半的点数要计算准确。

复杂度 可以发现 设一半点数为 w,那么搜索的状态总数为 C(n,w)*w!=1e7左右 加上sort是分部的 实际上加上一些剪枝还是可以过的。

const int MAXN=15;
int n,la,lb;ll L;
ll a[MAXN][MAXN];
vector<ll>g[MAXN][1<<MAXN];
inline void dfs(int x,int w,int s,ll v)
{
	if(v>L)return;
	if((w==la+1)||(w==lb+1))g[x][s].pb(v);
	if(w==la+1)return;
	rep(1,n,i)
	{
		if(s&(1<<(i-1)))continue;
		int ww=s|(1<<(i-1));
		dfs(i,w+1,ww,v+a[x][i]);
	}
}
int main()
{
		freopen("hamilton.in","r",stdin);
	freopen("hamilton.out","w",stdout);
	get(n);get(L);
	rep(1,n,i)rep(1,n,j)get(a[i][j]);
	//putl(a[1][2]);
	la=(n)/2;
	lb=(n)-la;
	if(la<lb)swap(la,lb);
	//put(la);put(lb);
	dfs(1,1,1,0);
	//putl(L);put(la);put(lb);
	int maxx=(1<<(n))-1;
	rep(1,n,i)rep(1,maxx,j)
	if(g[i][j].size())sort(g[i][j].begin(),g[i][j].end());
	rep(1,n,i)
	{
		rep(1,maxx,j)
		{
			if(!(j&(1<<(i-1))))continue;
			int s=(j^maxx)|1|(1<<(i-1));
			int r=g[i][s].size()-1;
			for(ui l=0;l<g[i][j].size();++l)
			{
				while(r>=0&&g[i][j][l]+g[i][s][r]>L)--r;
				if(r<0)break;
				if(g[i][j][l]+g[i][s][r]==L){puts("possible");return 0;}
			}
		}
	}
	puts("impossible");
	return 0;
}
原文地址:https://www.cnblogs.com/chdy/p/12707574.html