喵~ 主人好呀!今天本喵要为主人细致地讲解一道来自 Codeforces 的有趣问题:1761A - Two Permutations。这道题看起来有点绕,但只要跟着本喵的思路,一步步分析,就会发现它其实是一只很乖的题目哦!
题目大意
这道题是这样哒:
给你三个整数 n, a, b。你需要判断,是否存在两个长度为 n 的排列 p 和 q,同时满足下面两个条件:
p和q的最长公共前缀 (Longest Common Prefix) 长度恰好为a。p和q的最长公共后缀 (Longest Common Suffix) 长度恰好为b。
如果存在这样的一对排列,就输出 "Yes",不然就输出 "No",喵~
小提示喵:
- 排列:一个长度为
n的排列,就是包含从 1 到n每个整数恰好一次的数组。比如[1, 3, 2]是一个长度为 3 的排列。- 最长公共前缀:两个数组从第一个元素开始,连续相等的元素序列的最大长度。
- 最长公共后缀:两个数组从最后一个元素开始,连续相等的元素序列的最大长度。
题解方法
要解决这个问题,我们需要分两种情况来考虑,就像猫猫需要考虑是从左边还是右边跳上沙发一样,喵~ 这两种情况分别是:构造的两个排列 p 和 q 完全相同,或者它们不完全相同。
情况一:两个排列完全相同 (p = q)
如果 p 和 q 是同一个排列,比如 p = q = [1, 2, ..., n],那么它们从头到尾每个位置上的元素都一样。
- 它们的最长公共前缀就是整个排列,长度为
n。 - 它们的最长公共后缀也是整个排列,长度同样为
n。
所以,只有当题目给定的 a 和 b 同时等于 n 时,我们才可以通过构造两个完全相同的排列来满足条件。
结论一:如果
a == n并且b == n,答案是 "Yes"。
情况二:两个排列不完全相同 (p != q)
如果 p 和 q 不一样,情况就变得有趣起来了,喵~
让我们想象一下这两个排列的结构:
p: [ (公共前缀) | (中间部分) | (公共后缀) ]
q: [ (公共前缀) | (中间部分) | (公共后缀) ]- 公共前缀:长度为
a,p和q在这部分完全相同。 - 公共后缀:长度为
b,p和q在这部分也完全相同。
为了保证最长公共前缀恰好是 a,那么在第 a+1 个位置上,p 和 q 的元素必须不同(即 p[a+1] != q[a+1])。同理,为了保证最长公共后缀恰好是 b,在倒数第 b+1 个位置(即 n-b 位置)上,p 和 q 的元素也必须不同。
这意味着,p 和 q 的差异一定出现在既不是公共前缀也不是公共后缀的“中间部分”。
我们来数一数这个中间部分有多少个位置。总共有 n 个位置,去掉前面 a 个和后面 b 个,中间部分的位置数量就是 n - a - b。
如果
n - a - b < 0(即a + b > n): 这意味着前缀和后缀发生了重叠!比如n=4, a=3, b=3,前缀是[p1, p2, p3],后缀是[p2, p3, p4]。这本身就很难构造,因为用于前缀和后缀的元素集合必须不冲突。更重要的是,我们总共只有n个数,却需要a+b个不重复的数来填充不重叠的前后缀,这是不可能的。所以直接 "No"。如果
n - a - b = 0(即a + b = n): 这意味着前缀和后缀刚好接壤,没有中间部分。既然没有中间部分,p和q就没有地方可以变得不同了。因此p和q必须完全相同。但这又回到了情况一,此时a和b都必须是n。如果a或b小于n,则无解。如果
n - a - b = 1(即a + b = n - 1): 中间只有一个位置(第a+1个位置)和一个剩下的数字。p和q都只能把这个唯一的数字填在这个唯一的位置上。这就会导致p[a+1] == q[a+1],从而使得公共前缀的长度变成了a+1,不满足“恰好为a”的要求。所以这种情况也是 "No"。如果
n - a - b >= 2(即a + b <= n - 2): 太棒了!中间部分至少有两个位置和两个数字。比如中间有两个位置i和j,还有两个数字x和y。我们就可以在p中放p[i]=x, p[j]=y,而在q中放q[i]=y, q[j]=x。通过这样一个小小的交换,就保证了p和q在中间部分是不同的,从而满足了最长公共前缀/后缀的长度限制!是不是很聪明呀?喵~
结论二:如果
a + b <= n - 2,我们总能构造出满足条件的两个不同排列,答案是 "Yes"。
总结
把两种情况合起来,最终的判断条件就是:
如果 (a == n && b == n) 或者 (a + b <= n - 2),那么就存在这样的一对排列。否则,就不存在。
题解
下面是实现这个逻辑的 C++ 代码,本喵加上了可爱的注释哦~
#include <iostream>
void solve() {
int n, a, b;
std::cin >> n >> a >> b;
// 我们来判断是否存在这样的两个排列 p 和 q 喵~
//
// 情况一:p 和 q 完全相同 (p = q)
// 这种情况下,最长公共前缀和后缀的长度都是 n。
// 所以,只有当 a 和 b 都等于 n 时,这种情况才成立。
// 我们可以构造 p = q = [1, 2, ..., n]。
bool case1 = (a == n && b == n);
// 情况二:p 和 q 不相同 (p != q)
// 公共前缀占了 a 个位置,公共后缀占了 b 个位置。
// 为了让前缀长度 *恰好* 是 a,p[a+1] 必须不等于 q[a+1]。
// 这就要求 p 和 q 必须在非前缀和非后缀的“中间部分”有所不同。
//
// 中间部分的长度是 n - a - b。
// 如果 n - a - b < 2 (即 0 或 1),我们就没有足够空间来制造不同。
// - 如果是 0,前缀后缀相连,p和q只能相同,矛盾。
// - 如果是 1,中间只有一个位置和一个数,p和q也只能相同,矛盾。
// 如果 n - a - b >= 2,我们就有至少两个位置和两个数,
// 可以通过交换它们来构造出不同的中间部分,从而满足条件。
// 这个条件可以写成 a + b <= n - 2。
bool case2 = (a + b <= n - 2);
if (case1 || case2) {
std::cout << "Yes\n";
} else {
std::cout << "No\n";
}
}
int main() {
// 加速输入输出,让程序跑得像猫一样快!
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
int t;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}知识点介绍
这道题虽然简单,但也涉及了一些重要的概念呢,主人要记住喵!
排列 (Permutation) 排列是组合数学中的一个基本概念。简单来说,一个长度为
n的排列就是把从 1 到n的n个数字进行重排,每个数字必须使用一次且仅一次。就像把一排小鱼干重新排列,每条小鱼干都独一无二!最长公共前缀/后缀 (LCP/LCS) 这是字符串和序列问题中非常常见的概念。LCP 是指两个序列从开头算起最长的相同部分,LCS 则是从末尾算起。理解这两个概念是解决本题的关键。
构造性证明 (Constructive Proof) 本题的解法就是一种构造性思维。我们不需要真的把
p和q这两个排列完整地构造出来,只需要证明在满足某些条件时,这样的排列可以被构造出来。而在不满足条件时,证明它们无法被构造。这是一种在算法竞赛中非常强大和常用的思维方式哦!
好啦,这次的题解就到这里啦~ 主人学会了吗?如果还有不懂的,可以随时再来问本喵哦!喵~