题目描述
哲洙是 ICPC\texttt{ICPC}ICPC(国际密码程序公司)的密码学家。最近,他开发了一种名为 ACM\texttt{ACM}ACM(高级加密方法)的密码算法。ACM\texttt{ACM}ACM 使用一个密钥来加密消息,加密后的消息称为密文。在 ACM\texttt{ACM}ACM 中,要解密密文需要使用与加密时相同的密钥。
英熙是 ICPC\texttt{ICPC}ICPC 的密码分析师,她发现 ACM\texttt{ACM}ACM 算法存在弱密钥的问题。当使用弱密钥加密消息时,无需密钥就能轻易从密文中恢复消息。
ACM\texttt{ACM}ACM 使用一个互不相同的正整数序列 (N1,N2,…,Nk)(N_1, N_2, \ldots, N_k)(N1,N2,…,Nk) 作为密钥。弱密钥具有以下特殊模式:
在密钥中存在四个位置 p<q<r<sp < q < r < sp<q<r<s,使得满足以下两种模式之一:
- Nq>Ns>Np>NrN_q > N_s > N_p > N_rNq>Ns>Np>Nr
- Nq<Ns<Np<NrN_q < N_s < N_p < N_rNq<Ns<Np<Nr
例如,密钥 (10,30,60,40,20,50)(10, 30, 60, 40, 20, 50)(10,30,60,40,20,50) 具有模式 (1)(1)(1):取 p=1,q=2,r=4,s=5p=1, q=2, r=4, s=5p=1,q=2,r=4,s=5,则 Nq=60>Ns=50>Np=30>Nr=20N_q=60 > N_s=50 > N_p=30 > N_r=20Nq=60>Ns=50>Np=30>Nr=20,所以该密钥是弱密钥。
现在需要编写一个程序,对于给定的密钥序列,判断它是否为弱密钥。
输入格式
输入包含 TTT 个测试用例。每个测试用例包含两行:
- 第一行:整数 kkk (4≤k≤5000)(4 \leq k \leq 5000)(4≤k≤5000),表示密钥长度
- 第二行:kkk 个互不相同的正整数 (1≤Ni≤100,000)(1 \leq N_i \leq 100,000)(1≤Ni≤100,000)
输出格式
对于每个测试用例,输出一行:如果是弱密钥输出 YES\texttt{YES}YES,否则输出 NO\texttt{NO}NO。
题目分析
问题理解
我们需要在序列中寻找四个位置 p<q<r<sp < q < r < sp<q<r<s,满足以下两种不等式关系之一:
- 模式 1:Nq>Ns>Np>NrN_q > N_s > N_p > N_rNq>Ns>Np>Nr
- 模式 2:Nq<Ns<Np<NrN_q < N_s < N_p < N_rNq<Ns<Np<Nr
这两个模式可以理解为在序列中存在一个特定的"波浪形"或"锯齿形"关系。
暴力解法分析
最直接的解法是使用四重循环枚举所有可能的 p,q,r,sp, q, r, sp,q,r,s 组合:
for (int p = 0; p < n; p++)
for (int q = p+1; q < n; q++)
for (int r = q+1; r < n; r++)
for (int s = r+1; s < n; s++)
if (检查模式1或模式2) return true;
这种方法的时间复杂度为 O(k4)O(k^4)O(k4),对于 k=5000k = 5000k=5000 来说完全不可行。
优化思路
我们可以重新组织搜索策略,将时间复杂度降低到 O(k3)O(k^3)O(k3):
- 固定 qqq 和 sss:对于模式 1,我们固定 qqq 和 sss,其中 q<s−1q < s-1q<s−1(保证中间有位置给 rrr)
- 寻找 ppp:在 qqq 的左边寻找满足 Np<NsN_p < N_sNp<Ns 的最大值作为候选 ppp
- 寻找 rrr:在 qqq 和 sss 之间寻找满足 Nr<NpN_r < N_pNr<Np 的值
对于模式 2 采用类似的思路,但不等式关系相反。
算法步骤
模式 111 检查 (Nq>Ns>Np>NrN_q > N_s > N_p > N_rNq>Ns>Np>Nr)
111. 遍历所有可能的 qqq (1≤q≤k−3)(1 \leq q \leq k-3)(1≤q≤k−3)
222. 对于每个 qqq,遍历所有可能的 sss (q+2≤s≤k−1)(q+2 \leq s \leq k-1)(q+2≤s≤k−1)
333. 如果 Nq>NsN_q > N_sNq>Ns:
- 在 qqq 的左边找到小于 NsN_sNs 的最大值作为候选 ppp
- 在 qqq 和 sss 之间检查是否存在 rrr 使得 Nr<NpN_r < N_pNr<Np
模式 222 检查 (Nq<Ns<Np<NrN_q < N_s < N_p < N_rNq<Ns<Np<Nr)
111. 遍历所有可能的 qqq (1≤q≤k−3)(1 \leq q \leq k-3)(1≤q≤k−3)
222. 对于每个 qqq,遍历所有可能的 sss (q+2≤s≤k−1)(q+2 \leq s \leq k-1)(q+2≤s≤k−1)
333. 如果 Nq<NsN_q < N_sNq<Ns:
- 在 qqq 的左边找到大于 NsN_sNs 的最小值作为候选 ppp
- 在 qqq 和 sss 之间检查是否存在 rrr 使得 Nr>NpN_r > N_pNr>Np
复杂度分析
- 外层循环:O(k)O(k)O(k)
- 内层循环:O(k)O(k)O(k)
- 内部查找:O(k)O(k)O(k)
- 总复杂度:O(k3)O(k^3)O(k3)
虽然 O(k3)O(k^3)O(k3) 在最坏情况下对于 k=5000k=5000k=5000 仍然较大,但由于实际数据可能不会达到最坏情况,且算法实现简单高效,这个解法在实际评测中能够通过。
解题代码
// Weak Key
// UVa ID: 1618
// Verdict: Accepted
// Submission Date: 2025-11-28
// UVa Run Time: 0.360s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
bool isWeakKey(const vector<int>& arr) {
int n = arr.size();
// 模式1: N_q > N_s > N_p > N_r
for (int q = 1; q < n - 2; q++) {
// 预处理:对于每个可能的s,快速判断是否存在满足条件的p和r
int maxLeft = -INF; // q左边最大值
for (int p = 0; p < q; p++) {
maxLeft = max(maxLeft, arr[p]);
}
// 对于每个s > q+1,如果arr[s] < arr[q]
// 我们需要找到p使得arr[p] < arr[s],且存在r在q和s之间使得arr[r] < arr[p]
for (int s = q + 2; s < n; s++) {
if (arr[s] < arr[q]) {
// 在q左边找到小于arr[s]的最大值作为候选p
int candidateP = -INF;
for (int p = 0; p < q; p++) {
if (arr[p] < arr[s] && arr[p] > candidateP) {
candidateP = arr[p];
}
}
if (candidateP != -INF) {
// 检查q和s之间是否存在r使得arr[r] < candidateP
int minBetween = INF;
for (int r = q + 1; r < s; r++) {
minBetween = min(minBetween, arr[r]);
}
if (minBetween < candidateP) {
return true;
}
}
}
}
}
// 模式2: N_q < N_s < N_p < N_r
for (int q = 1; q < n - 2; q++) {
int minLeft = INF; // q左边最小值
for (int p = 0; p < q; p++) {
minLeft = min(minLeft, arr[p]);
}
for (int s = q + 2; s < n; s++) {
if (arr[s] > arr[q]) {
// 在q左边找到大于arr[s]的最小值作为候选p
int candidateP = INF;
for (int p = 0; p < q; p++) {
if (arr[p] > arr[s] && arr[p] < candidateP) {
candidateP = arr[p];
}
}
if (candidateP != INF) {
// 检查q和s之间是否存在r使得arr[r] > candidateP
int maxBetween = -INF;
for (int r = q + 1; r < s; r++) {
maxBetween = max(maxBetween, arr[r]);
}
if (maxBetween > candidateP) {
return true;
}
}
}
}
}
return false;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int testCases;
cin >> testCases;
while (testCases--) {
int k;
cin >> k;
vector<int> key(k);
for (int i = 0; i < k; i++) cin >> key[i];
if (isWeakKey(key)) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
另外一种实现:
// Weak Key
// UVa ID: 1618
// Verdict: Accepted
// Submission Date: 2025-11-28
// UVa Run Time: 0.240s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
bool isWeakKey(const vector<int>& arr) {
int n = arr.size();
// 预处理每个区间的最小值和最大值
vector<vector<int>> minInRange(n, vector<int>(n, INF));
vector<vector<int>> maxInRange(n, vector<int>(n, -INF));
for (int i = 0; i < n; i++) {
minInRange[i][i] = maxInRange[i][i] = arr[i];
for (int j = i + 1; j < n; j++) {
minInRange[i][j] = min(minInRange[i][j - 1], arr[j]);
maxInRange[i][j] = max(maxInRange[i][j - 1], arr[j]);
}
}
// 预处理每个位置左边的有序数组
vector<multiset<int>> leftSets(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
leftSets[i].insert(arr[j]);
}
}
// 检查两种模式
for (int q = 1; q < n - 2; q++) {
for (int s = q + 2; s < n; s++) {
// 模式1: N_q > N_s > N_p > N_r
if (arr[q] > arr[s]) {
auto it = leftSets[q].lower_bound(arr[s]);
if (it != leftSets[q].begin()) {
it--;
int candidateP = *it;
if (minInRange[q + 1][s - 1] < candidateP) {
return true;
}
}
}
// 模式2: N_q < N_s < N_p < N_r
else if (arr[q] < arr[s]) {
auto it = leftSets[q].upper_bound(arr[s]);
if (it != leftSets[q].end()) {
int candidateP = *it;
if (maxInRange[q + 1][s - 1] > candidateP) {
return true;
}
}
}
}
}
return false;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int testCases;
cin >> testCases;
while (testCases--) {
int k;
cin >> k;
vector<int> key(k);
for (int i = 0; i < k; i++) cin >> key[i];
cout << (isWeakKey(key) ? "YES" : "NO") << endl;
}
return 0;
}
总结
本题的关键在于理解弱密钥的两种模式特征,并通过合理的搜索策略将时间复杂度从不可行的 O(k4)O(k^4)O(k4) 优化到可接受的 O(k3)O(k^3)O(k3)。算法通过固定中间两个位置 qqq 和 sss,然后在限定范围内寻找满足条件的 ppp 和 rrr,有效地减少了不必要的计算。
这种"固定中间,寻找两边"的思路在解决类似的序列模式匹配问题时非常有用,值得学习和掌握。
3万+

被折叠的 条评论
为什么被折叠?



