T1. 廊橋分配
考慮分別求出分配給 " 國內區 " 與 " 國際區 " \(i\) 個廊橋時能停靠的飛機數 \(f_i\) 與 \(g_i\)。
則答案為 \(\max\limits_{0 \leq i \leq n} \{ f_i + g_{n - i} \}\)。
注意到求 \(f, g\) 兩個數組的過程是一樣的,這里以求 \(f\) 數組為例。
一開始國內區沒有廊橋。
考慮將每個飛機的「抵達時間」和「起飛時間」放入一個序列后排序,使用類似掃描線的方法求解:
-
若遇到的是一個飛機的「抵達時間」。
- 若當前沒有可供停靠的廊橋,則新建一個廊橋停靠;
否則找一個當前沒有停靠飛機、且編號最小的廊橋停靠(使用小根堆維護當前可供停靠的廊橋編號)。 - 然后該將貢獻計入該廊橋中。
- 若當前沒有可供停靠的廊橋,則新建一個廊橋停靠;
-
若遇到的是一個飛機的「起飛時間」。則將該飛機停靠的廊橋的編號放入小根堆。
最后對這個貢獻數組求一遍前綴和即可求出 \(f\) 數組。
時間復雜度 \(\mathcal{O}(n \log n)\)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
inline int read() {
int x = 0, f = 1; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); }
while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
return x * f;
}
const int N = 100100;
int n, m1, m2;
int bel[N];
int len;
struct Node {
int x, state, id;
Node() {}
Node(int A, int B, int C) : x(A), state(B), id(C) {}
} seq[N * 2];
bool cmp(Node A, Node B) {
return A.x < B.x;
}
int f[N], g[N];
int num[N];
int cnt;
priority_queue<int> q;
void solve(int m, int f[N]) {
len = 0, cnt = 0;
while (q.size()) q.pop();
memset(bel, 0, sizeof(bel));
memset(num, 0, sizeof(num));
for (int i = 1; i <= m; i ++) {
int l = read(), r = read();
seq[++ len] = (Node) { l, 0, i };
seq[++ len] = (Node) { r, 1, i };
}
sort(seq + 1, seq + 1 + len, cmp);
for (int i = 1; i <= len; i ++) {
if (seq[i].state) {
q.push(-bel[seq[i].id]);
} else {
if (q.size()) {
int id = -q.top(); q.pop();
bel[seq[i].id] = id;
num[id] ++;
} else {
int id = ++ cnt;
bel[seq[i].id] = id;
num[id] ++;
}
}
}
for (int i = 1; i <= m; i ++) f[i] = f[i - 1] + num[i];
}
int main() {
n = read(), m1 = read(), m2 = read();
solve(m1, f);
solve(m2, g);
int ans = 0;
for (int i = 0; i <= n; i ++) {
int x = i, y = n - i;
if (x > m1) x = m1;
if (y < 0) y = 0; if (y > m2) y = m2;
ans = max(ans, f[x] + g[y]);
}
printf("%d\n", ans);
return 0;
}
T2. 括號序列
顯然 dp。線性 dp 不易處理題目中的變換規則,可以考慮區間 dp。
首先可以 \(\mathcal{O}(n^2)\) 預處理一個數組 \(ok(i, j)\) 表示:區間 \([i, j]\) 中的字符串能否表示為不超過 \(k\) 個字符 *
的非空字符串。
然后定義 dp 狀態:
- \(f_0(l, r)\):表示區間 \([l, r]\) 中的字符,能組成多少個符合規范的超級括號序列。
- \(f_1(l, r)\):表示區間 \([l, r]\) 中的字符,能組成多少個形如
SA
的超級括號序列。 - \(f_2(l, r)\):表示區間 \([l, r]\) 中的字符,能組成多少個形如
AS
的超級括號序列。 - \(f_3(l, r)\):表示區間 \([l, r]\) 中的字符,能組成多少個形如
(A)
的超級括號序列。
- 對於 \(f_0\) 的轉移:
- 第一種是形如
(...)
的轉移。 - 第二種是形如
AB
、ASB
的轉移。
為了做到不重復計數(例如ABC
可以看成A + BC
和AB + C
,但是這兩種轉移本質上是一個方案),我們可以強行認為AB
、ASB
中的A
是形如(...)
的串,這樣就不會重復計數了。
- 第一種是形如
- 對於 \(f_1\) 的轉移:
- 首先要判斷一下 \(s_l\) 是否為字符
(
或?
的其中一個,\(s_r\) 是否為字符)
或?
的其中一個。
若不滿足,則 \(f_1(l, r) = 0\)。 - 第一種是形如
(*...*)
的轉移。 - 第二種是形如
(A)
的轉移。 - 第三種是形如
(SA)
的轉移。 - 第四種是形如
(AS)
的轉移。
- 首先要判斷一下 \(s_l\) 是否為字符
- 對於 \(f_2\) 的轉移:
- 對於 \(f_3\) 的轉移:
最后的答案即為 \(f_0(1, n)\),時間復雜度 \(\mathcal{O}(n^3)\)。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
const int mod = 1e9 + 7;
int n, k;
char s[N];
bool ok[N][N];
bool inc(int i, int j) {
if (s[i] != '(' && s[i] != '?') return 0;
if (s[j] != ')' && s[j] != '?') return 0;
return 1;
}
long long f[4][N][N];
int main() {
scanf("%d%d", &n, &k);
scanf("%s", s + 1);
for (int i = 1; i <= n; i ++)
for (int j = i; j <= n; j ++) {
if (j - i >= k) break;
if (s[j] != '*' && s[j] != '?') break;
ok[i][j] = 1;
}
for (int i = 1; i < n; i ++)
if (inc(i, i + 1)) f[0][i][i + 1] = f[1][i][i + 1] = 1;
for (int len = 3; len <= n; len ++)
for (int l = 1; l <= n - len + 1; l ++) {
int r = l + len - 1;
// f_1: (...)
if (inc(l, r)) {
if (ok[l + 1][r - 1]) f[1][l][r] = 1;
f[1][l][r] += f[0][l + 1][r - 1];
f[1][l][r] += f[2][l + 1][r - 1];
f[1][l][r] += f[3][l + 1][r - 1];
f[1][l][r] %= mod;
}
// f_2: SA
for (int k = l; k < r; k ++) {
if (!ok[l][k]) break;
f[2][l][r] = (f[2][l][r] + f[0][k + 1][r]) % mod;
}
// f_3: AS
for (int k = r; k > l; k --) {
if (!ok[k][r]) break;
f[3][l][r] = (f[3][l][r] + f[0][l][k - 1]) % mod;
}
// f_0: all
f[0][l][r] = f[1][l][r];
for (int k = l; k < r; k ++)
f[0][l][r] = (f[0][l][r] + f[1][l][k] * f[0][k + 1][r]) % mod;
for (int k = l; k < r; k ++)
f[0][l][r] = (f[0][l][r] + f[1][l][k] * f[2][k + 1][r]) % mod;
}
printf("%d\n", f[0][1][n]);
return 0;
}
T3. 回文
因為第一步的操作至多只有兩種,所以可以對第一步的操作進行分類討論。
這里以第一步執行操作 1(取最左邊的數)為例,第一步執行操作 2 同理。
注意到題目中 \(b\) 序列是回文序列,一定有 \(b_{i} = b_{2n + 1 - i}\)。
所以在執行第 \(i\) 次操作的時候,一定要保證第 \(2n + 1 - i\) 次操作時可以找到一個數與之對應。於是我們可以在考慮第 \(i\) 次操作時,即刻判斷第 \(2n + 1 - i\) 次操作。
因為第一步執行了操作 1,顯然有 \(b_1 = b_{2n} = a_1\),於是我們可以找到一個數 \(\text{target} \neq 1\),使得 \(a_{\text{target}} = a_1\)。
將 \(a\) 序列分成兩段 \([2, \text{target})\)、\((\text{target}, 2n]\),分別記這兩段區間為 \(A, B\)。其中 \(A, B\) 的實際意義分別為:\(a\) 序列在 左半段 / 右半段 還可以選的數的區間。
顯然,對於任意的 \(i(2 \leq i \leq n)\):
- 第 \(i\) 次操作只可以從「\(A\) 的左端」或「\(B\) 的右端」取。
然后,這一次取的數將會從「\(A\) 的左端」或「\(B\) 的右端」彈出。 - 第 \(2n + 1 - i\) 次操作只可以從「\(A\) 的右端」或「\(B\) 的左端」取。
然后,這一次取的數將會從「\(A\) 的右端」或「\(B\) 的左端」彈出。
於是我們可以使用兩個雙端隊列 \(\mathtt{Q_A}, \mathtt{Q_B}\) 來維護區間 \(A, B\) 里的數。
起初隊列 \(\mathtt{Q_A}\) 從隊頭到隊尾是 \(a_2 \to a_{\text{target} - 1}\),隊列 \(\mathtt{Q_B}\) 從隊頭到隊尾是 \(a_{2n} \to a_{\text{target} + 1}\)。
那么,對於任意的 \(i(2 \leq i \leq n)\),第 \(i\) 次操作與第 \(2n + 1 - i\) 次操作選的數,一定即出現在 \(\mathtt{Q_A}\) 或 \(\mathtt{Q_B}\) 的隊頭,又出現在 \(\mathtt{Q_A}\) 或 \(\mathtt{Q_B}\) 的隊尾。在找不到這樣的數的時候,就無解;在有多種選數方案時,選擇字典序最小的那一種。
時間復雜度 \(\mathcal{O}(n)\)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
inline int read() {
int x = 0, f = 1; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); }
while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); }
return x * f;
}
const int N = 500100;
int n;
int a[N * 2];
int target;
char ans[N * 2];
deque<int> qA, qB;
bool solve() {
for (int i = 2; i <= n; i ++) {
if (qA.size() == 0) {
if (qB.front() == qB.back())
ans[i] = 'R', ans[2 * n + 1 - i] = 'R',
qB.pop_front(), qB.pop_back();
else
return 0;
} else if (qB.size() == 0) {
if (qA.front() == qA.back())
ans[i] = 'L', ans[2 * n + 1 - i] = 'L',
qA.pop_front(), qA.pop_back();
else
return 0;
} else {
if (qA.size() > 1 && qA.front() == qA.back()) {
ans[i] = 'L', ans[2 * n + 1 - i] = 'L',
qA.pop_front(), qA.pop_back();
continue;
} else if (qA.front() == qB.back()) {
ans[i] = 'L', ans[2 * n + 1 - i] = 'R',
qA.pop_front(), qB.pop_back();
continue;
}
if (qB.front() == qA.back()) {
ans[i] = 'R', ans[2 * n + 1 - i] = 'L',
qB.pop_front(), qA.pop_back();
continue;
} else if (qB.size() > 1 && qB.front() == qB.back()) {
ans[i] = 'R', ans[2 * n + 1 - i] = 'R',
qB.pop_front(), qB.pop_back();
continue;
}
return 0;
}
}
return 1;
}
void work() {
n = read();
for (int i = 1; i <= 2 * n; i ++)
a[i] = read();
/* ----- ----- left ----- ----- */
while (qA.size()) qA.pop_back();
while (qB.size()) qB.pop_back();
ans[1] = 'L', ans[2 * n] = 'L';
for (int i = 2; i <= 2 * n; i ++)
if (a[i] == a[1]) { target = i; break; }
for (int i = 2; i < target; i ++) qA.push_back(a[i]);
for (int i = 2 * n; i > target; i --) qB.push_back(a[i]);
if (solve()) {
for (int i = 1; i <= 2 * n; i ++) printf("%c", ans[i]);
puts("");
return;
}
/* ----- ----- right ----- ----- */
while (qA.size()) qA.pop_back();
while (qB.size()) qB.pop_back();
ans[1] = 'R', ans[2 * n] = 'L';
for (int i = 1; i < 2 * n; i ++)
if (a[i] == a[2 * n]) { target = i; break; }
for (int i = 1; i < target; i ++) qA.push_back(a[i]);
for (int i = 2 * n - 1; i > target; i --) qB.push_back(a[i]);
if (solve()) {
for (int i = 1; i <= 2 * n; i ++) printf("%c", ans[i]);
puts("");
return;
}
/* ----- ----- No ----- ----- */
puts("-1");
}
int main() {
int T = read();
while (T --)
work();
return 0;
}
T4. 交通規划
(待填 ...)