#6043. 「雅礼集训 2017 Day7」蛐蛐国的修墙方案

本文探讨了如何解决蛐蛐国领导人提出的修墙问题。通过分析合法括号序列及节点间的关系,采用特殊搜索策略找到了一种有效的解决方案。

#6043. 「雅礼集训 2017 Day7」蛐蛐国的修墙方案

内存限制:1024 MiB 时间限制:2000 ms 标准输入输出
题目类型:传统 评测方式:Special Judge
上传者: 匿名
题目描述

Do you wanna build a wall?

在离跳蚤国很远的地方有一个蛐蛐国,最近蛐蛐国选出了一位新的领导人。

这位领导人上任之后做的第一件事,就是在蛐蛐国和它的一个邻国 —— 蝈蝈国之间修一堵围墙。

围墙可以看成是一个长度为 n nn 的括号序列,与此同时还有一个长度为 n nn 的排列 P PP,一个围墙被称为稳的,当且仅当:

  1. 这个括号序列是合法的;
  2. 构造一张 n nn 个点的图,当且仅当第 i ii 个位置是左括号时,点 i ii 向点 Pi P_iPi 连边,最后形成的图必须满足每个点的度数均为一。

保证对于任意 i ii 有 i≠Pi i \neq P_iiPi

一个括号序列合法的定义如下:

  1. 空序列是合法的;
  2. 如果 A AA 是合法的,那么 (A) 也是合法的;
  3. 如果 A AA 和 B BB 都是合法的,那么 AB ABAB 也是合法的。

例如 ()()((()())) 是合法的,而 ())(() 不是。

现在蛐蛐国的领导人想知道一种合法的修墙方案。

输入格式

第一行一个整数 n nn,表示括号序列的长度。

接下来一行 n nn 个正整数表示排列 Pi P_iPi

输出格式

输出一行一个长度为 n nn 的括号序列,如果有多种解,输出任意一种即可。

注意,样例输出只是一种参考解,解可能并不唯一。

样例
样例输入 1
6
2 3 6 1 4 5
样例输出 1
()()()
数据范围与提示

本题有三个子任务,只有通过一个子任务中的全部测试点才能获得该子任务的所有分数。

子任务 1:n=20 n = 20n=20,10 分;
子任务 2:n=40 n = 40n=40,30 分;
子任务 3:n=100 n = 100n=100,60 分。


n<=100,看上去不能搜。

优化一下,发现这是由一些环形的关系组成的(因为题目保证有解,所以一定是一些偶环,自然也没有单个点的存在)

而每一个环形关系中,只要确定了一个就可以确定全部,那么最坏情况是环长度都为2,

计算量在2^50,不能接受

同时发现,长度为2时可以贪心,前面的为(,后面的为)

(合法的括号序列等价于左括号和右括号数量相等且在任意一个前缀中“(“数量不少于“)“)

所以长度为2的环反而成了我们的优势(可以特判)这也是绑点的原因吧,不然没法同时卡找环爆搜和贪心加爆搜,就会被骗很多分(出题人良心)

然后两种结合一下,那么最坏情况是环长度都为4,2^25

你还可以加点剪枝(但是一般只能减到2^23左右,就最后几个剪剪)

也算一种打暴力的思路吧;

另外,DFS搜索时,最后一种情况处理之后也应注意是否还原,如果这个选择和后面的选择相关,一定要还原!

如果无关,比如你写的是那种搜出一种完整方案再最后统一检查答案的(也就是说放弃了启发式的剪枝),随便吧。


ACcode:

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 205
using namespace std;

char ANS[3]=")(";
int loop[maxn][maxn],cnt,len[maxn],bel[maxn];
int n;
int Prev[maxn*2],to[maxn*2],info[maxn],cnt_e;
int lp[maxn],tot;
bool vis[maxn];
int ans[maxn];

void Node(int u,int v){
	cnt_e++;
	Prev[cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v;
}

void dfs(int now,int ff){
	if(vis[now]) return;
	vis[now]=1;
	lp[tot++]=now;
	for(int i=info[now];i;i=Prev[i])
		if(ff!=to[i])
			dfs(to[i],now);
}

void ser(int now){
	tot=0;
	dfs(now,-1);
	if(tot==2)
		ans[min(lp[0],lp[1])]=1,ans[max(lp[0],lp[1])]=-1;
	else{
		len[++cnt]=tot;
		for(int i=0;i<tot;i++) 
			loop[cnt][i]=lp[i],bel[lp[i]]=cnt;
	}
}

bool flag=0;
void Dfs(int now,int sum){
	if(sum<0) return;
	if(now==n+1){
		for(int i=1;i<=n;i++)
			putchar(ANS[ans[i]==1]);
		flag=1;
		return ;
	}
	if(!ans[now]){
		ans[loop[bel[now]][0]]=1;
		for(int i=1;i<len[bel[now]];i++)
			ans[loop[bel[now]][i]]=-ans[loop[bel[now]][i-1]];
		Dfs(now+1,sum+ans[now]);
		if(flag) return;
		
		for(int i=0;i<len[bel[now]];i++) ans[loop[bel[now]][i]]=-ans[loop[bel[now]][i]];
		Dfs(now+1,sum+ans[now]);
		if(flag) return;
			
		for(int i=0;i<len[bel[now]];i++) ans[loop[bel[now]][i]]=0;
	}
	else Dfs(now+1,sum+ans[now]);
}

int main(){
	int u;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&u);
		Node(u,i),Node(i,u);
	}
	for(int i=1;i<=n;i++)
		if(!vis[i])
			ser(i);
	
	Dfs(1,0);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值