題目鏈接:B. Uniqueness
題意:n (1 - 2000) 個數,只刪除一個子串使得數組中沒有重複元素,求刪除的子串長度。
思路(參考博客cf1208B B. Uniqueness):
逆向考慮問題,要刪除的最少區間長度對應最多能保留多少個數,又因爲區間要是連續的,所以只能刪除左端區間,或中區間,或右邊區間。利用兩個map,第一個存從左往右能保留的每個元素的下標,並用一個數記錄最終下標;第二個map從右往左掃描,用於記錄當前元素的右半部分能保留多少數,如果出現與其右部分的重複元素,則計算其中間要刪除的元素個數,並判斷是否要更新答案,否則進行標記,若當前右半部分的元素與一開始左半部分的元素,則模擬對左半部分元素刪除操作,同時更新刪除元素個數找出最佳答案,具體情況看代碼和註釋。
暴力的話直接枚舉所有可能的長度,二分的話就是二分可能的長度,就是再暴力的基礎上多了一點優化。
原博主代碼:
#include <bits/stdc++.h>
using namespace std;
map<int, int > book1, book2;
int main() {
int n;
int a[2005];
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int ans = 99999;
// p記錄左半部分能保留數字的下標(即個數), q記錄右半部分能保留到的下標,q-p則爲要刪除的序列的大小
int p = 1, q = n;
// 從左往右掃描,記錄從左往右能保留多少個數,即遇到重複數字停止掃描
while (p <= n) {
if (book1[a[p]]) {
break;
} else {
book1[a[p]] = p;
}
p++;
}
// 由於掃描的時候是判斷下一位,所以p要減一纔是實際最後那個不重複元素的下標(個數)
p--;
ans = n - p; // 此時要刪除的元素爲元素總個數減去前左半部分要保留的數字
// 從右往左掃描
while (q >= 1) {
if (book2[a[q]]) { // 如果q右半部分的數字在左半部分沒出現過,但q右半部分的數字出現重複,則進行判斷後結束掃描
if (ans > q - p) ans = q - p; // 如果此時要刪除的元素個數比之前少,則更新答案
break;
} else { // 否則進行標記該元素出現過
book2[a[q]] = 1;
}
if (book1[a[q]] && p >= book1[a[q]]) { // 如果右半部分的最左端的元素與一開始左半部分要保留的數字重複,
//則模擬左半部分元素刪除操作,同時判斷是否要更新答案和更新左半部分要保留的數字下標
if (ans > q - p) ans = q - p;
p = book1[a[q]] - 1;
}
q--;
}
printf("%d\n", ans);
return 0;
}
我就貼暴力的代碼了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2010;
int n;
int a[N];
map<int, int> ma;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int ans = n - 1;
for (int i = 1; i <= n; i++)
{
ma.clear();
bool ok = true;
for (int j = 1; j < i; j++)
{
ma[a[j]]++;
if (ma[a[j]] == 2)
{
ok = false;
break;
}
}
if (!ok)
continue;
int mn = n + 1;
for (int j = n; j >= i; j--)
{
ma[a[j]]++;
if (ma[a[j]] == 1)
{
mn = j;
}
else
break;
}
ans = min(ans, mn - i);
}
cout << ans << endl;
return 0;
}