APCS. 2019/02/16
2019/02/19 APCS實作題詳解及範例程式
pA
無確切題目敘述及輸入格式。
pB
Description
給定一個長度為$n(1<n\le10^5)$的01字串,計算最長與最短的連續1長度。
接著有$k(1 \le k \le 2*10^4)$次操作,每次將一個0改成1。
對於每次操作,輸出當前最長與最短的連續1長度(包括一開始輸入的)。
Solution
題目中有特別說到保證連續1的長度不超過$10^4$,所以可以用線性做法,不過這裡是考慮到當沒有這個限制時,應該怎麼做。
使用Disjoint Set(並查集)紀錄連通塊(相鄰的1為同一塊),接著用multiset紀錄每個連通塊的大小。
對於每次輸入,把該點的兩邊(注意要考慮到可能只有一邊是1的可能)的大小在multiset中去掉,然後用Disjoint Set合併,再將新的大小插入到multiset裡面。
輸出的時候輸出set中最大最小值即可。
時間複雜度:$O(n + klogn)$
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10010;
int n, k;
int in[maxn];
multiset<int> s;
struct disjoint_set {
int parent[maxn];
int size[maxn];
void init() {
for (int i = 0; i <= maxn; i++) {
parent[i] = i;
size[i] = 1;
}
}
int find(int i) {
if (i == parent[i])
return i;
return parent[i] = find(parent[i]);
}
void connect(int a, int b) {
int pa = find(a);
int pb = find(b);
s.erase(s.find(size[pa]));
s.erase(s.find(size[pb]));
size[pa] += size[pb];
parent[pb] = pa;
s.insert(size[pa]);
}
}ds;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> k;
ds.init();
for (int i = 1; i <= n; i++) {
cin >> in[i];
if (in[i])
s.insert(1);
}
for (int i = 2; i <= n; i++) {
if (in[i - 1] && in[i]) {
ds.connect(i - 1, i);
}
}
cout << *s.begin() << ' ' << *s.rbegin() << '\n';
while (k--) {
int i;
cin >> i;
ds.size[i] = 1;
s.insert(1);
if (i > 1 && in[i - 1])
ds.connect(i - 1, i);
if (i < n && in[i + 1])
ds.connect(i, i + 1);
in[i] = 1;
cout << *s.begin() << ' ' << *s.rbegin() << '\n';
}
return 0;
}
pC
Description
給你三個數值函數f,g,h的定義,變數數目各為1,2,3,且保證均為一次函數。輸入一個三者的合成函數但所有括號與逗點都被空白取代,計算該合成函數的值。題目中提示可以用遞迴,並保證不必使用大數運算。
Solution
遞迴做下去。
由於我沒有f,g,h的係數,所以在程式中會標示為$f0, g0, g1, h0, h1, h2$代表各個係數。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f0, g[2], h[3];
ll toll(string &s) {
ll re = 0;
for (char &i : s) {
re *= 10;
re += i - '0';
}
return re;
}
ll calc(char cmd) {
string s;
ll re = 0;
if (cmd == 'f') {
cin >> s;
if (isdigit(s[0]))
re = f0 * toll(s);
else
re = f0 * calc(s[0]);
}else if (cmd == 'g') {
for (int i = 0; i < 2; i++) {
cin >> s;
if (isdigit(s[0]))
re += g[i] * toll(s);
else
re += g[i] * calc(s[0]);
}
}else
for (int i = 0; i < 3; i++) {
cin >> s;
if (isdigit(s[0]))
re += h[i] * toll(s);
else
re += h[i] * calc(s[0]);
}
return re;
}
void init() {
// 測試用係數
f0 = 2;
g[0] = 1, g[1] = 2;
h[0] = 1, h[1] = 3, h[2] = 4;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
// init();
char cmd;
cin >> cmd;
cout << calc(cmd) << '\n';
return 0;
}
pD
Description
給定$n(1<n<2*10^5)$,及長度為$n$的陣列a
和b
$(0 \le a[i], b[i] \le 10^7)$。
$S(i)=i-max(j)-1 使得 a[i]+b[i]<a[j] 且 j<i (若j不存在,則j=0)$。
求$\sum_{i=1}^n S(i)$為何。
Solution
從前面做到後面,假設當前index為$i$,則可以刪掉一些在$i$之前「不可能為答案」的數字。
這樣講解有點抽象,從下面的圖片比較容易懂:
考慮$a[1]\text{~}a[5]$,可以發現$a[2]和a[4]$不可能為答案,因為$a[5]$比這兩個數字都還大。
所以可以把灰色部分砍掉,維護一個「遞減陣列」。
而有了這個遞減陣列,就可以利用$a[6]+b[6]$去二分搜,找出最大的j滿足條件。
不過到這裡還沒完,假設$S(i)$算完了,那應該要想辦法把$a[i]$加入該陣列。對於一個現成的遞減陣列,要怎麼加入新的元素?
一直檢查該陣列的後面,如果小於$a[i]$,則pop掉,否則就把$a[i]$加到最後面即可。
時間複雜度:$O(nlogn)$
注意:因為upper_bound預設是用在「遞增陣列」,並非遞減。所以我這裡在upper_bound
時使用rbegin
和rend
,是因為我是維護一個遞減數列,但是upper_bound
只適用遞增數列,所以就用反向的iterator來處理。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200010;
int a[maxn], b[maxn];
int n;
ll ans;
struct node {
int num, index;
node() {}
node (int a, int b) {
num = a, index = b;
}
bool operator< (const node &b) const {
return num < b.num;
}
};
vector<node> arr;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
for (int i = 1; i <= n; i++) {
auto ptr = upper_bound(arr.rbegin(), arr.rend(), node(a[i] + b[i], 0));
if (ptr == arr.rend())
ans += i - 1;
else
ans += i - ptr->index - 1;
while (!arr.empty() && arr.back().num < a[i])
arr.pop_back();
arr.push_back({a[i], i});
}
cout << ans << '\n';
return 0;
}