把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4005】小 Y 和地铁(搜索)

点此看题面

  • 给定二维平面上一条含\(n\)个关键点的线段,每个点有一个标号,同一标号最多出现两次。
  • 要求在每对相同标号的点之间连一条边(不同的边不能有重合的部分),求线段外交点数的最小值。
  • 数据组数\(\le100,n\le44\)

无脑的暴搜?

刚看到这道题的时候第一想法,就是每对相同标号的点之间的连边只有两种可能,即分别在两边,不是直接暴搜就好了吗?!

然后仔细看了眼样例解释,发现样例二很良心地给出了一个\(Hack\)数据,就是实际上还可以这样连:

这样一来每对标号相同的点就有四种连法了(两个点各自都有向上和向下两种选法),无脑暴搜肯定复杂度炸裂。

剪枝的暴搜!

考虑对于图中蓝红两种连边方式:

发现它俩联合起来形成了一个包围左端点之后整个后缀的环。

这意味着,右端点作出了选择后,无论我们左端点选择向上还是向下,之后的连边选择中要么与这两种方式都无交,要么都有交,并不影响后续的决策。

所以对于左端点我们可以直接根据与之前连成的边产生交点数贪心决策,这样一来每个点真的只有两种可能了,可以直接暴搜。

要求交点数,以图中右端点选择向上为例,蓝线交点数就是\([l,r]\)中之前选择向上的右端点数,红线交点数就是\([r,n]\)中之前选择向上的右端点数+\([l,n]\)中之前选择向下的右端点数。(这里计算交点数只用考虑右端点的选择,进一步验证了左端点的选择不影响后续的决策)

考虑到每次要询问某种选法的点对在一段区间内的右端点数,只需开两个变量\(A,B\),第\(i\)位存储序列中第\(i\)位是否为之前选择向上/向下的一个右端点。

预处理出\(2^{\frac n2}\)内所有数二进制下\(1\)的个数,查询时只需利用位运算抠出这段区间,然后把这段区间掰成两半查询\(1\)的个数相加即可。

代码:\(O(T2^{\frac n2})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 44
#define M 22
#define LL long long
using namespace std;
int n,a[N+5],ed[N+5],g[1<<M];
I int Calc(Con LL& A,CI x,CI y) {LL t=A>>x&((1LL<<y-x+1)-1);return g[(t&((1<<M)-1))]+g[t>>M];}//计算A在[x,y]内1的个数
int ans;I void dfs(CI x,CI t,Con LL& A,Con LL& B)//暴搜
{
	if(t>=ans) return;if(x>n) return (void)(ans=t);if(x==ed[a[x]]) return dfs(x+1,t,A,B);//不优于已知答案;搜索结束;当前是右端点已讨论过
	dfs(x+1,t+min(Calc(A,x,ed[a[x]]),Calc(A,ed[a[x]],n)+Calc(B,x,n)),A|(1LL<<ed[a[x]]),B);//右端点选择向上,贪心决策左端点
	dfs(x+1,t+min(Calc(B,x,ed[a[x]]),Calc(B,ed[a[x]],n)+Calc(A,x,n)),A,B|(1LL<<ed[a[x]]));//右端点选择向下,贪心决策左端点
}
int main()
{
	RI i;for(i=0;i^(1<<M);++i) g[i]=g[i>>1]+(i&1);//预处理每个数二进制下1的个数
	RI Tt;scanf("%d",&Tt);W(Tt--)
	{
		for(scanf("%d",&n),i=1;i<=n;++i) ed[i]=0;for(i=1;i<=n;++i) scanf("%d",a+i),ed[a[i]]=i;//ed记录每种数对应的右端点
		ans=1e9,dfs(1,0,0,0),printf("%d\n",ans);
	}return 0;
}
posted @ 2021-06-01 20:36  TheLostWeak  阅读(32)  评论(0编辑  收藏  举报