洛谷 P4005 小 Y 和地铁 题解

Reference

题目入口:传送门

本文在写作时有参考 这篇题解 ,在此表示感谢。

前置知识

  1. 树状数组
  2. 大法师(DFS)

Problem

简化版题意:给定n个点,其中每个点可能对应另外一个点。如果一个点有对应点,那么就要用曲线连接这两个点。这些曲线会有许多交点(不存在环、三线共点、交叉但没有交点这三种情况),求交点最少个数。

Solution

根据数据范围可以看出是二进制暴力搜索,看题解里 dalao 都用的是模拟退火云云,但是我不会,所以······

大法师+剪枝!~

优化1

不难发现,两个点最多有6种连线方式,时间复杂度O(6(n/2))。

我们发现,如果把2、3画在一起,他们一定是一个圆环,且包住了整条线段。假如有一条新的线段,则那条线段要么和2、3都没交点,要么都有交点。也就是说对于两个点,用2还是用3对答案的贡献都是一样的。

同理,4、5也一样。

这样就变成了4种。

优化2

我们试着将1和2画在一起,又是一个环,但这个环有点特殊,对在这两个点左边的点右侧的线段影响相同(在将所有线段排序后进行 DFS ),但左侧的点对于1或2的答案贡献是不同的。

所以,我们只需在 DFS 时,计算1、2的产生的答案贡献哪个更少,4、6同理。

优化3

在前两个优化的基础上加入树状数组,复杂度O(2(n/2))。

Code

//P4005 小 Y 和地铁
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cctype>
#include<algorithm>
#include<sstream>
#include<map>
#include<list>
//#include<windows.h>
#include<stack>
#include<queue>
#include<utility> 
#include<climits> 
#include<vector>
namespace TREEARR//树状数组模板
{
	inline int lowbit(int x)
	{
		return x&-x;
	}
	struct Treearr
	{
		int c[1000005],siz;
		void init(int p)
		{
			for(int i=1;i<=p;i++)
			{
				this->c[i]=0;
			}
			this->siz=p;
			return;
		}
		void add(int x,int d)
		{
			while(x<=siz)
			{
				this->c[x]+=d;
				x+=lowbit(x);
			}
			return;
		}
		int sum(int x)
		{
			int res=0;
			while(x)
			{
				res+=this->c[x];
				x-=lowbit(x);
			}
			return res;
		}
		int query(int l,int r)
		{
			return this->sum(r)-this->sum(l-1);
		}
	};
}
using namespace std;
int t,n,a[45],pos,l[45],r[45],ans;
TREEARR::Treearr up,down;
void work_dfs(int st,int sum)//DFS
{
	int a1,a2;
	if(st>pos)		//找到可行解
	{
		ans=min(ans,sum);//取最小值
		return;
	}
	if(sum>ans)//一处小剪枝,当sum>ans时,即使有可行解也找不到比ans更优的,所以直接返回
	{
		return;
	}
	a1=min(up.query(l[st],r[st]),down.query(l[st],n)+up.query(r[st],n));//第一种情况
    up.add(r[st],1);
	work_dfs(st+1,sum+a1);
	up.add(r[st],-1);
    a2=min(down.query(l[st],r[st]),up.query(l[st],n)+down.query(r[st],n));//第二种情况
    down.add(r[st],1);
	work_dfs(st+1,sum+a2);
	down.add(r[st],-1);
	return;
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		ans=1<<30;//注意每次的初始化
		for(int j=1;j<=n;j++)
		{
			scanf("%d",a+j);//等同于scanf("%d",&a[j]);
		}
		pos=0;
		for(int j=1;j<=n;j++)
		{
			for(int k=j+2;k<=n;k++)
			{
				if(a[k]==a[j])
				{
					l[++pos]=j;
					r[pos]=k;
					break;
				}
			}
		}
		up.init(n);
		down.init(n);
		work_dfs(1,0);
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2021-10-30 21:29  Day_Dreamer_D  阅读(100)  评论(1)    收藏  举报