对于一些组合最优化问题,我们可按照一个简单的策略,逐步得到一个最优解。
这个策略告诉我们每一步应该怎么做。
一般来说,每一步所做的事情是相似的。
我们把这样的策略叫做贪心策略。
泛指那些寻求一个(按某种标准)最优的组合对象的问题。组合对象包括
有
尝试提出一个安排人坐摩天轮的策略。
这样安排是最优解吗?为什么?
要证明一个贪心策略是对的,只要证明存在一个最优解包含此策略第一步的选择。
常用调整法来证明。
考虑一个最优解,如果它不包含此策略第一步的选择,调整成有第一步的选择,并且证明调整之后的解不比原来的最优解差。
写法一
int main() {
int n, x;
cin >> n >> x;
vector<int> w(n);
for (int i = 0; i < n; i++)
cin >> w[i];
sort(w.begin(), w.end());
int l = 0, r = n - 1;
int ans = 0;
while (l < r) { //双指针
if (w[l] + w[r] <= x) {
l++;
r--;
} else {
r--;
}
ans++;
}
if (l == r)
ans++;
cout << ans << '\n';
}
写法二
int main() {
int n, x;
cin >> n >> x;
vector<int> w(n);
for (int i = 0; i < n; i++)
cin >> w[i];
sort(w.begin(), w.end());
int l = 0, r = n - 1;
int ans = 0;
while (l <= r) { //双指针
if (w[l] + w[r] <= x) {
l++;
r--;
} else {
r--;
}
ans++;
}
cout << ans << '\n';
}
我们在寻找或理解一个贪心策略时,可以从“第一步怎么做?”入手。
通常,做了第一步以后,我们面临一个和原问题形式相同但规模更小的问题(子问题)。
贪心策略的关键常常是按照某种规则对所考虑东西进行排序。
有
制作一个支架需要两根木棍。用长度为
校长计划购买
请你告诉校长购买的单个艺术品的重量最大是多少。
不难看出应该用最长的
考虑四根木棍的情形,我们发现应该把最长和最短的木棍配成一对。
设四根木棍的长度是
可见
对于一般情况,设最短木棍的长度是
考虑一个最优解。如果其中
贪心策略:每次拿最长和最短的木棍配成一对。
有
甲乙二人都采取最优策略。求最后甲拿到的数的总和。
最优策略:每个人每次都拿剩下的数中最小的那个。
证明:对
设
直线上有
从这
按从左到右的顺序考虑这些点。按以下策略选取下一个要标记的点
void solve(int n, int r) {
vector<int> x(n);
for (int i = 0; i < n; i++)
cin >> x[i];
sort(x.begin(), x.end());
int ans = 0;
for (int i = 0; i < n; ) {
int last = x[i];
while (i < n && x[i] - last <= r)
i++;
int p = x[i - 1]; //在p处打上标记
ans++;
while (i < n && x[i] - p <= r)
i++;
}
cout << ans << '\n';
}
用小木棍拼成的数字 0 到 9 如下
0 1 2 3 4 5 6 7 8 9
现在要用小木棍拼出一个正整数,满足下列条件:
若存在满足以上条件的正整数,输出它,否则输出 -1。
你要处理
要让拼出的数尽可能小,
首先要让这个数的位数尽可能少。
需要小木棍最多的数字是 8,它需要
最好是用
其次是让第一个数字尽可能小。
第一个目标达成的前提下,第一个数字最好是1,其次是2,……
第三是让第二个数字尽可能小。
前两个目标达成的前提下,第二个数字最好是 0,其次是1,……
……
此外,注意到拼 6,9 不如拼 0,拼3,5 不如拼 2。
考虑按
0:拼成
1:
2:
3:
4:
5:
6:
vector<int> need = {6, 2, 5,5, 4, 5, 6, 3, 7, 6};
vector<int> best(100000);
for (int x = 1; x <= 10000; x++) {
int y = 0;
for (char c : to_string(x))
y += need[c - '0'];
// 拼 x 需要 y 根木棍
if (best[y] == 0)
best[y] = x; // 用 y 根木棍能拼成的最小正整数是 x
}
for (int i = 1; i <= 20; i++)
cout << best[i] << ' ';
输出:
0 1 7 4 2 6 8 10 18 22 20 28 68 88 108 188 200 208 288 688
这道题,获得最优解的策略不是“每一步都如何如何”就足以描述的,而是分层次的。最优策略的表述形如
void solve() {
int n; cin >> n; int r = n % 7;
if (r == 0) cout << string(n / 7, '8');
else if (r == 1) {
if (n == 1) cout << -1;
else cout << 10 << string((n - 8) / 7, '8');
} else if (r == 2) cout << 1 << string(n / 7, '8');
else if (r == 3) {
if (n == 3) cout << 7;
else if (n == 10) cout << 22;
else cout << 200 << string((n - 17) / 7, '8');
} else if (r == 4) {
if (n == 4) cout << 4;
else cout << 20 << string((n - 11) / 7, '8');
} else if (r == 5) cout << 2 << string(n / 7, '8');
else if (r == 6) cout << 6 << string(n / 7, '8');
cout << '\n';
}
void solve() {
int n; cin >> n; int r = n % 7, len = (n + 6) / 7;
string ans;
if (r == 0) ans = string(len, '8');
else if (r == 1)
ans = n == 1 ? "-1"s : "10" + string(len - 2, '8');
else if (r == 2) ans = "1" + string(len - 1, '8');
else if (r == 3)
ans = n == 3 ? "7"s : n == 10 ? "22"s : "200" + string(len - 3, '8');
else if (r == 4)
ans = n == 4 ? "4"s : "20" + string(len - 2, '8');
else if (r == 5) ans = "2" + string(len - 1, '8');
else if (r == 6) ans = "6" + string(len - 1, '8');
cout << ans << '\n';
}
有
在可选的区间中,每次都选取右端点最小的区间。
struct S { int l, r; };
bool cmp(S a, S b) { return a.r < b.r; }
int solve() {
int n; cin >> n;
vector<S> a(n);
for (int i = 0; i < n; i++)
cin >> a[i].l >> a[i].r;
sort(a.begin(), a.end(), cmp);
int ans = 0, r = 0;
for (S s : a)
if (r <= s.l) {
ans++;
r = s.r;
}
return ans;
}
数轴上有
前面的《萨鲁曼的部队》可以看作区间选点问题的一个特例。
如果某区间内已经有一个点被取到,则称此区间已经被满足。
把区间按右端点从小到大排序。按此顺序考虑每个区间,若当前区间未被满足,则选取它的右端点。
数轴上有
struct S { int l, r; }
S a[maxn];
bool cmp(S a, S b) { return a.l < b.l; }
int solve(int n, int x, int y) {
sort(a, a + n, cmp);
int r = 0, ans = 0;
// x < y
for (int i = 0; i < n; ) {
while (i < n && a[i].l <= x) {
r = max(r, a[i].r);
i++;
}
if (r <= x) return -1; // 没有进展
ans++; //选一个区间
if (r >= y) return ans; // 够了
x = r;
}
return -1;
}
一天有
只能把空闲的奶牛安排出来值班。每个时段必需有奶牛在值班。
最少需要动用多少奶牛参与值班?如果没有办法安排出合理的方案,就输出
这道题里的区间指的是一段连续的整数(离散),而不是数轴上的区间(连续)。在处理区间端点时要注意和《区间覆盖问题 1》的区别。
struct S { int l, r; };
S a[maxn];
bool cmp(S a, S b) { return a.l < b.l; }
int solve(int n, int x, int y) {
sort(a, a + n, cmp);
int r = 0, ans = 0;
// x <= y
for (int i = 0; i < n; ) {
while (i < n && a[i].l <= x) {
r = max(r, a[i].r);
i++;
}
if (r < x) return -1; // 没有进展
ans++; //选一个区间
if (r >= y) return ans; // 够了
x = r + 1;
}
return -1;
}
int main() {
int N, T;
cin >> N >> T;
for (int i = 0; i < N; i++)
cin >> a[i].l >> a[i].r;
cout << solve(N, 1, T) << '\n';
}
表示范围(序列里面连续的一段)时,采用左闭右开的方式比较好。
比如一列东西里从第
struct S { int l, r; };
S a[maxn];
bool cmp(S a, S b) { return a.l < b.l; }
int solve(int n, int x, int y) {
sort(a, a + n, cmp);
int r = 0, ans = 0;
// x <= y
for (int i = 0; i < n; ) {
while (i < n && a[i].l <= x) {
r = max(r, a[i].r);
i++;
}
if (r <= x) return -1; // 没有进展
ans++; //选一个区间
if (r >= y) return ans; // 够了
x = r;
}
return -1;
}
int main() {
int N, T;
cin >> N >> T;
for (int i = 0; i < N; i++) {
cin >> a[i].l >> a[i].r;
a[i].r += 1;
}
cout << solve(N, 1, T + 1) << '\n';
}
数轴上有
struct S { int l, r; };
bool cmp(S a, S b) {
if (a.l != b.l) return a.l < b.l;
return a.r > b.r;
}
S a[maxn];
int n;
vector<int> solve() {
sort(a, a + n, cmp);
vector<int> take = {0};
int i = 1;
while (i < n) {
int last_r = a[take.back()].r;
if (a[i].r <= last_r) { i++; continue; }
if (a[i].l > last_r) { take.push_back(i); i++; }
else {
int j = i; i++;
while (i < n && a[i].l <= last_r) {
if (a[i].r > a[j].r) j = i;
i++;
}
take.push_back(j);
}
}
return take;
}
有
今要把这些公寓分配给申请者,不能把一间公寓分配给多个申请者。
最多能满足多少个申请者?
数轴上有
点
求最多能配成多少对。
注意到区间的长度都相同,可采用以下策略
const int maxn = 2e5 + 5;
int a[maxn], b[maxn];
int main() {
int n, m, k;
cin >> n >> m >> k;
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < m; i++)
cin >> b[i];
sort(a, a + n);
sort(b, b + m);
int ans = 0;
int j = 0;
// 双指针
for (int i = 0; i < m; i++) {
while (j < n && b[i] > a[j] + k)
j++;
if (j < n && b[i] >= a[j] - k) {
ans++;
j++;
}
}
cout << ans << '\n';
}
数轴上有
点
求最多能配成多少对。
按从小到大的顺序考虑每个点,如果有包含它的区间,那么选择右端点最小的那个区间和它配对。
int X[maxm], L[maxn], R[maxn];
bool used[maxn];
int M, N;
int solve() {
sort(X, X + M);
int ans = 0;
for (int i = 0; i < M; i++) {
int k = -1;
for (int j = 0; j < N; j++)
if (!used[j] && L[j] <= X[i] && X[i] <= R[j])
if (k == -1 || R[j] < R[k])
k = j;
if (k != -1) { used[k] = true; ans++; }
}
return ans;
}
时间:
想要快速实现上述过程,我们可使用 C++ 标准库提供的优先队列 std::priority_queue。
设类型 T 具有小于号 <。
priority_queue<T> q;
q 是一个存放 T 类型的东西的容器。它支持下列操作:
设类型 T 具有小于号 <。实际上,定义
priority_queue<T> q;
的完整形式是
priority_queue<T, vector<T>, less<T>> q;
若把 less<T> 换成 greater<T>,q.top() 就返回 q 的一个最小元素。
int j = 0;
for (int i = 0; i < M; i++) {
while (j < N && L[j] <= X[i]) { q.push(R[j]); j++; }
while (!q.empty()) {
int r = q.top(); q.pop();
if (r >= X[i]) { ans++; break; }
}
}
int X[maxn];
pair<int, int> LR[maxn];
int N, M;
int solve() {
sort(X, X + M); sort(LR, LR + N);
priority_queue<int, vector<int>, greater<int>> q;
int ans = 0, j = 0;
for (int i = 0; i < M; i++) {
while (j < N && LR[j].first <= X[i]) {
q.push(LR[j].second); j++;
}
while (!q.empty()) {
int r = q.top(); q.pop();
if (r >= X[i]) { ans++; break; }
}
}
return ans;
}
有
每个盒子最多能容纳一个球。
判断能否把所有
你需要解决
不难看出,在这个问题中,盒子相当于点,球相当于区间。
不过这里有
pair<int, int> LR[maxn];
int N;//有N个区间
bool solve() {
sort(LR, LR + N);
priority_queue<int, vector<int>, greater<int>> q;
int i = 1, j = 0;
for (int t = 0; t < N; t++) {
if (q.empty())
i = LR[j].first;//跳过用不上的点
while (j < N && LR[j].first <= i) {
q.push(LR[j].second);
j++;
}
if (q.top() < i)
return false;
q.pop();
i++;
}
return true;
}
pair<int, int> LR[maxn];
int N;//有N个区间
bool solve() {
sort(LR, LR + N);
priority_queue<int, vector<int>, greater<int>> q;
int i = LR[0].first, j = 0;
for (int t = 0; t < N; t++) {
while (j < N && LR[j].first <= i) {
q.push(LR[j].second);
j++;
}
if (q.top() < i)
return false;
q.pop();
if (q.empty())
i = LR[j].first;//跳过用不上的点
else i++;
}
return true;
}
数轴上有
点
今要选一些点和区间配对。求配对的区间的权值之和的最大值。
这个问题较难,我们先来看一个简单的特殊情况。
你到商店去买
你有
每张代金券只能使用一次。不能把多张代金券用在同一个物品上,也不能把一张代金券用在多个物品上。
买
价格相当于点,代金券
目标:让用出去的代金券的总优惠金额最大。
原则:
贪心策略:对于最小的价格,如果能用代金券,应该用
算法:
int main() {
int n, m; cin >> n >> m;
vector<int> p(n);
long long sum = 0;
for (int i = 0; i < n; i++) {
cin >> p[i];
sum += p[i];
}
vector<pair<int,int>> a(m);
for (int i = 0; i < m; i++)
cin >> a[i].first;
for (int i = 0; i < m; i++)
cin >> a[i].second;
sort(p.begin(), p.end());
sort(a.begin(), a.end());
priority_queue<int> d;
int i = 0;
for (int x : p) {
while (i < m && a[i].first <= x) {
d.push(a[i++].second);
}
if (!d.empty()) {
sum -= d.top();
d.pop();
}
}
cout << sum << '\n';
}
把优惠金额最大的代金券用在适用的物品中价格最小的那个。
为了实现此策略,我们需要把所有物品的价格,即整数
C++标准库就提供满足上述需求的容器 std::multiset。
multiset(多重集)意谓容许重复元素的集合。
设类型 T 具有小于号 <,multiset<T> s; 是一种通用容器,支持下列操作
struct S { int l, d; };
bool cmp(S a, S b) { return a.d > b.d; }
int main() {
int n, m; cin >> n >> m;
long long sum = 0;
multiset<int> p;
for (int i = 0; i < n; i++) { int x; cin >> x; p.insert(x); sum += x; }
vector<pair<int, int>> a(m);
for (int i = 0; i < m; i++) cin >> a[i].l;
for (int i = 0; i < m; i++) cin >> a[i].d;
sort(a.begin(), a.end(), cmp);
for (int i = 0; i < m; i++) {
auto it = p.lower_bound(a[i].l);
if (it != p.end()) {//能把券i用掉
sum -= a[i].d;
p.erase(it);
}
}
cout << sum << '\n';
}
有
问题一:最少需要分成多少组?
问题二:给出一个组数最少的分组方法。
输入
5
1 10
2 4
3 6
5 8
4 7
输出
4
1
2
3
2
4
用区间安排问题的策略,每次选出最多个互不相交的区间作为一组。
五个区间,如下图所示。

要构造一个最小分组方案,可采取以下贪心策略:
你能证明此策略的正确性吗?
struct S { int l, r, id; };
bool operator<(S x, S y) { return x.l < y.l; }
struct T { int r, group_id; };
bool operator<(T x, T y) { return x.r > y.r; }
void solve() {
int n; cin >> n;
vector<S> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i].l >> a[i].r;
a[i].id = i;
}
sort(a.begin(), a.end());
priority_queue<T> q;
vector<int> gid(n);
int cnt = 0;
for (S t : a) {
if (q.empty() || q.top().r >= t.l) {
gid[t.id] = ++cnt; //新增一组
q.push({t.r, cnt});
}
else {
int id = q.top().group_id;
q.pop();
q.push({t.r, id});
gid[t.id] = id;
}
}
cout << cnt << '\n';
for (int x : gid)
cout << x << '\n';
}
沿用上面的思路,我们可用下述方法一次构造出一组来:
struct S { int l, r, id; };
bool operator<(S x, S y) { return x.l < y.l; }
void solve() {
int n; cin >> n;
multiset<S> a;
for (int i = 0; i < n; i++) {
int l, r; cin >> l >> r;
a.insert({l, r, i});
}
int cnt = 0;
vector<int> gid(n);
while (!a.empty()) {
++cnt;
auto it = a.begin();
do {
gid[it->id] = cnt;
auto it2 = a.upper_bound({it->r, 0, 0});
a.erase(it);
it = it2;
} while (it != a.end());
}
cout << cnt << '\n';
for (int x : gid)
cout << x << '\n';
}
某新建的机场分为国内区和国际区,有
每架飞机到达后,如果相应的区还有空闲廊桥,就停靠在廊桥,否则就停靠在远机位。
有
你要分配廊桥,使停靠廊桥的飞机数量尽可能多。
求能够停靠廊桥的飞机数量的最大值。
国内区和国际区是独立的,可分别考虑。以下只考虑国内区。
假设分配给国内区足够多的廊桥,给这些廊桥编号 1,2,3,……
每架国内航班飞机到达后,若国内区有空闲廊桥,就停靠在编号最小的那个。
这样,每架国内航班飞机停靠在哪个廊桥是确定的。
沿用区间分组问题的算法,我们先找出停靠在 1 号廊桥的那些飞机,然后在余下的飞机中找出停靠在 2 号廊桥的那些飞机,如此等等。
于是我们知道如果只有一个廊桥,有几架飞机可以停靠,如果只有两个廊桥,有几架飞机可以停靠,等等。
对将要到来的国内区航班飞机,用区间分组问题的解法二,对每个
计算
答案是
int main() {
int n, m1, m2;
cin >> n >> m1 >> m2;
vector<int> s1 = solve(m1, n);
vector<int> s2 = solve(m2, n);
int ans = 0;
for (int i = 0; i <= n; i++)
ans = max(ans, s1[i] + s2[n - i]);
cout << ans << '\n';
}
vector<int> solve(int m, int n) {
set<pair<int,int>> t;
for (int i = 0; i < m; i++) {
int a, b; cin >> a >> b;
t.insert({a, b});
}
// cnt[i]:停在第i个廊桥的飞机的数量
vector<int> cnt(n + 1);
for (int i = 1; i <= n; i++) {
int x = 0;// 当前时刻
while (1) {
auto it = t.upper_bound({x, INT_MAX});
if (it == t.end())
break;
cnt[i]++;
x = it->second;
t.erase(it);
}
}
for (int i = 1; i <= n; i++)
cnt[i] += cnt[i - 1];
return cnt;
}
同样的思路,还有另一个算法。
考虑国内区。假设分配给它
把一架飞机的到达和离开当成两个事件,
按发生时间从早到晚的顺序枚举这些事件,用一个小优先队列存当前空闲廊桥的编号。
struct E {// 事件
//时间
int t;
//类型:到达1,离开-1
int type;
// 飞机的编号
int id;
};
bool cmp(E a, E b) {
return a.t < b.t;
}
vector<int> solve(int m, int n) { // m:飞机数量, n: 廊桥数量
priority_queue<int, vector<int>, greater<int>> lq; // 空余廊桥
for (int i = 1; i <= n; i++)
lq.push(i);
vector<E> event; // 事件
for (int i = 0; i < m; i++) {
int arrive, leave; cin >> arrive >> leave;
event.push_back({arrive, 1, i});
event.push_back({leave, -1, i});
}
sort(event.begin(), event.end(), cmp);
vector<int> cnt(n + 1); // cnt[i]:使用廊桥i的飞机的数量
vector<int> b(m); // b[i]:编号i的飞机使用了哪个廊桥
for (E e : event)
if (e.type == 1) {
if (lq.empty()) continue;
b[e.id] = lq.top();
cnt[lq.top()]++;
lq.pop();
} else if (b[e.id]) lq.push(b[e.id]);
for (int i = 1; i <= n; i++)
cnt[i] += cnt[i - 1];
return cnt;
}
我们有一个长为
样例 1
4 3
2 3 1 3
2 1 3
样例 2
4 4
2 3 1 4
2 3 1 4
样例 3
20 10
6 3 8 5 8 10 9 3 6 1 8 3 3 7 4 7 2 7 8 5
3 5 8 10 9 6 1 4 2 7
首要目标是让排列的第一个数尽可能小。
序列
找出
对
设
那么
取
确定了最优解的第一项以后,就变成一个规模减小一的子问题。
代码怎么写?
vector<int> a(n);
vector<int> cnt(m + 1);
for (int x : a)
cnt[x]++;
vector<int> ans;
vector<bool> taken(m + 1);
for (int x : a) {
if (!taken[x]) {
while (!ans.empty() && cnt[ans.back()] > 0 && ans.back() > x) {
// ans.back() 比 x 大,并且后面还有,所以让它排在 x 后面
taken[ans.back()] = false;
ans.pop_back();
}
ans.push_back(x);
taken[x] = true;
}
cnt[x]--;
}
for (int x : ans)
cout << x << ' ';
cout << '\n';
给你一个长为
对每个
输入
输出
P3728 是弱化版。
把答案,即序列
根据按字典序比大小的规则,考虑如何确定
// 返回 a 的本质不同的非空子序列中字典序第 k 小的,k从1开始数
// 若无解,返回空序列。
// 0 <= a[i] <= m - 1
vector<int> kth_subsequence(vector<int> a, int m, long long k) {
int n = a.size();
// 第一步:计算一堆东西
vector<long long> f(n + 1);
// f[i]:s[i..n-1] 中以 a[i] 开头的子序列的数量
vector<int> next(n, n);
// next[i]:a[i] 后面第一个等于 a[i] 的元素的位置,若不存在,next[i] = n。
vector<int> last(m, n); // last[x]:上一个 x 的位置,0 <= x <= m - 1
long long tot = 1; // 空序列
for (int i = n - 1; i >= 0; i--) {
f[i] = tot;
if (tot < k + 1) { // tot 增长到 k + 1 就够了
tot = tot - f[last[a[i]]] + f[i];
if (tot > k + 1)
tot = k + 1;
}
next[i] = last[a[i]];
last[a[i]] = i;
}
if (tot < k + 1) return {}; // 无解
// 第二步:贪心
list<int> p; // 用一个链表来做
for (int x = 0; x < m; x++) {
if (last[x] < n)
p.push_back(last[x]);
}
vector<int> ans;
int i = 0;
while (k > 0) {
auto it = p.begin();
while (1) {
while (*it < i) {
*it = next[*it];
}
if (*it == n) {
it = p.erase(it);
continue;
}
if (f[*it] < k) {
k -= f[*it];
++it;
} else {
ans.push_back(a[*it]);
k--;
i = *it + 1;
break;
}
}
}
return ans;
}
有
我们用下述指标来评价一个摞牛的顺序:
求最优排列里每头牛的风险的最大值。
我们用
如果
特别地,交换
设
确切地说,交换
把两边的
我们尝试把小于号左边拆开,写成
由于
再把小于号右边拆开,写成
前者即
或写成
如果排列
那么
你能证明吗?
struct Cow { int w, s; };
bool cmp(Cow a, Cow b) {
return a.w + a.s < b.w + b.s;
}
void solve() {
int n;
cin >> n;
vector<Cow> a(n);
for (int i = 0; i < n; i++)
cin >> a[i].w >> a[i].s;
sort(a.begin(), a.end(), cmp);
int sum = 0, ans = INT_MIN;
for (int i = 0; i < n; i++) {
ans = max(ans, sum - a[i].s);
sum += a[i].w;
}
cout << ans << '\n';
}
Duda 收集了
能量石的能量会衰减。第
Duda 通过吃能量石最多能获得多少能量?
有
给你
5
(
))
((
))
(
1
5
3
4
2
4
))
(
((
))(()
2
3
4
1
一个括号序列是平衡的当且仅当它满足下列两个条件
我们可以把这两个条件写成数学式子:把一个括号序列转化成一个正负一序列,( 对应 ) 对应
有这样一个解法:
先看左右括号总数是不是一样多。
对每个括号序列,先把能配对的左右括号消掉。这样,一个括号序列就变成若干个右括号后面接若干个左括号。例如 ())())(() 变成 ))(, ((() 变成 ((。
用数对 ))( 是 (( 是
现在来考虑如何排序。
排序方法:
第三点,怎么看出来要按
一个办法是猜。注意到第二点跟
第二个办法是利用对称性。如果从右往左看,) 就相当于从左往右看的 (,( 就相当于从左往右看的 )。
第三个办法是交换相邻两项来推导。
先看有两个这样的括号序列怎么排列更好。
设
对于排列
而对于排列
如果第一个排列好于第二个,就是说
即
即
给你
我们给出拼数问题的一般的表述:
字符集
对于
定理:
证明:验证严格若序的四个性质。irreflexivity 和 asymmetry 是自明的。transitivity of incomparability,即
类似地,由
abc214_e
([P2859](https://www.luogu.com.cn/problem/P2859) Stall Reservations)
由左括号 “(” 和右括号 “)” 构成的字符串称为**括号序列**。一个**平衡括号序列**是 - (),或者 - (A),其中 A 是平衡括号序列,或者 - AB,其中 A,B 都是平衡括号序列。