1 条题解
-
0
题目大意
我们有一个排列
P(保证P_i ≠ i),需要构造一个长度为n的合法括号序列。 规则是:对于序列中的每个位置i,如果它是'(',则必须在图中从节点i向节点P_i连一条边。最终要求这个图里每个节点的度数都为 1。关键条件分析
-
括号序列合法:这是基本要求。
-
每个点度数为 1:这意味着整个图是由若干个环组成的(因为每个点入度和出度之和为1,且排列保证了图的特殊性)。更具体地说:
- 我们只在
i位置是'('时,才从i连向P_i。 - 一个节点
j的入度可能来自所有满足P_i = j且s[i] = '('的i。 - 一个节点
j的出度来自于它自己:如果s[j] = '(',那么它会连向P_j。 - "每个点度数为一" 在这个问题背景下,实际上等价于:每个节点在图中恰好是一条边的起点或终点。 更精确地说,由于我们只在左括号位置连边,这个条件意味着:
- 整个图必须是一个完美匹配:
n个节点,n/2条边(因为括号序列中左括号数量为n/2),每个节点恰好与一条边关联。 - 如果把连边关系
(i, P_i)看作一个匹配,那么这个匹配必须覆盖所有点。
- 整个图必须是一个完美匹配:
- 我们只在
-
排列
P的启示:排列P将1..n这个集合映射到自身。我们可以将(i, P_i)视为一个潜在的边。我们的任务是选择其中恰好n/2条边(通过将对应的i设为'('来实现),使得:- 被选中的边构成一个完美匹配(即所有节点恰好出现一次)。
- 这个匹配与括号序列的合法性相容。
核心思路
-
将问题转化为图论问题: 我们把
1到n这n个点画出来。排列P定义了一个置换,这个置换可以分解成若干个环(Cycle)。 -
观察环的性质: 考虑排列
P分解出的一个长度为L的环:a1 -> a2 -> a3 -> ... -> aL -> a1。- 如果我们选择环上的一个点
ai作为左括号(即选择边(ai, P[ai]=a{i+1})),那么节点ai和a{i+1}都已经被这条边使用了。 - 为了不重复使用节点,环上下一个点
a{i+1}就不能再作为左括号了(否则a{i+1}会连出两条边,且a{i+2}会被两条边指向)。所以,我们必须间隔地选择环上的点作为左括号。
- 如果我们选择环上的一个点
-
环长度的奇偶性至关重要:
- 对于一个偶数长度的环,我们可以间隔地选择点,恰好选出
L/2个点作为左括号,剩下的L/2个点作为右括号的“接收者”。这是可行的。 - 对于一个奇数长度的环,我们无法进行这样的间隔选择。因为无论从哪里开始,尝试间隔选取都会导致冲突,无法满足每个点度数恰好为1的条件。因此,如果排列
P中存在奇数长度的环,则问题无解。
- 对于一个偶数长度的环,我们可以间隔地选择点,恰好选出
-
构造括号序列:
- 如果所有环的长度都是偶数,那么对于每个环,我们固定一种选取模式(例如,从环上编号最小的点开始,将它标记为左括号,然后间隔标记)。
- 具体步骤:
- 分解排列
P得到所有环。 - 检查每个环的长度,如果存在奇数环,直接判定无解(但题目保证有解,所以可以跳过检查直接构造,或者题目数据保证有解)。
- 遍历每个环,将环上的点按顺序排列。将奇数位(1, 3, 5, ...)的点标记为
'(',偶数位(2, 4, 6, ...)的点标记为')'。或者反过来也可以,只要保证间隔选取且整个序列左右括号数量相等。 - 输出每个位置
i对应的括号。
- 分解排列
算法正确性证明
- 每个点度数为1: 在我们的构造中,每个环上,一个点如果是左括号,它是一条边的起点;如果它是右括号,由于它在环中的前一个点必然是左括号,而那个左括号的边正好指向它,所以它是那条边的终点。每个点都恰好属于一条边。
- 括号序列合法: 我们将每个环自身构造成了
()()... 这种交替形式。整个括号序列是由多个这样的合法交替序列拼接而成。任意多个合法括号序列的拼接仍然是合法的。
样例验证
输入:
n = 6 P = [2, 3, 6, 1, 4, 5] (索引从1开始)- 找环:
- 从1开始:
1 -> P[1]=2 -> P[2]=3 -> P[3]=6 -> P[6]=5 -> P[5]=4 -> P[4]=1。得到一个环:1 -> 2 -> 3 -> 6 -> 5 -> 4。长度为6,是偶数。
- 从1开始:
- 构造括号序列:
- 环的顺序:
[1, 2, 3, 6, 5, 4] - 将奇数位置(第1、3、5个)标记为
'(',偶数位置(第2、4、6个)标记为')'。 - 即:
- 位置1:
'(' - 位置2:
')' - 位置3:
'(' - 位置6:
')'(注意这里是环序列的第4个元素,对应原序列位置6) - 位置5:
'('(环序列第5个元素,对应原序列位置5) - 位置4:
')'(环序列第6个元素,对应原序列位置4)
- 位置1:
- 环的顺序:
- 输出:按原序列位置
1到6输出括号:- s[1] =
'(' - s[2] =
')' - s[3] =
'(' - s[4] =
')' - s[5] =
'(' - s[6] =
')' - 得到
()()(),与样例输出一致。
- s[1] =
复杂度分析
- 时间复杂度:O(n)。只需要遍历节点找环,并为每个节点分配括号。
- 空间复杂度:O(n)。需要存储访问标记、排列
P和结果字符串。
代码实现 (C++)
#include <iostream> #include <vector> #include <string> using namespace std; int main() { int n; cin >> n; vector<int> P(n + 1); // 1-indexed for (int i = 1; i <= n; i++) { cin >> P[i]; } vector<char> s(n + 1, ' '); vector<bool> visited(n + 1, false); for (int i = 1; i <= n; i++) { if (!visited[i]) { // Find the cycle starting from i vector<int> cycle; int cur = i; while (!visited[cur]) { visited[cur] = true; cycle.push_back(cur); cur = P[cur]; } // Assign parentheses for the cycle int len = cycle.size(); // If cycle length is odd, it's impossible (but problem guarantees solution) for (int j = 0; j < len; j++) { // Even index in cycle (0-based) -> '(', Odd index -> ')' if (j % 2 == 0) { s[cycle[j]] = '('; } else { s[cycle[j]] = ')'; } } } } for (int i = 1; i <= n; i++) { cout << s[i]; } cout << endl; return 0; }总结
这道题的关键在于将抽象的括号序列和连边条件,转化为在排列的环结构上进行间隔选取的图论问题。通过分析环的奇偶性,并给出一种确定的构造方法,就能简洁地解决问题。
-
- 1
信息
- ID
- 5054
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 7
- 已通过
- 1
- 上传者