林尚廷
No Code No Life :: A competitive programmer
講者的心聲:今年好難QQ
雖說是2019的題解,但是觀念教學性質還是比較多QQ
這次內容有點多,能講多少算多少吧
greedy method / 貪心法
binary search / 二分搜尋
dynamic programming / 動態規劃
standard template library (STL)
prefix sum / 前綴和
coordinate compression / 離散化
程式競賽要注意的小細節
這已經是第\(N\)次放這張圖片了 OAO
這張圖片也放了很多次 QQ
#include<bits/stdc++.h>
using namespace std;
int main() {
int number;
scanf("%d", &number)
printf("%d\n", number);
}
int main() {
int number;
cin >> number;
cout << number << '\n';
}
int main() {
int x = 2147483647;
long long y = 9223372036854775807;
cout << x << ' ' << y << '\n';
}
#include<bits/stdc++.h>
#define int long long
using namespace std;
int main() {
int a[10000000] = {};
cout << a[9999999] << endl;
}
int a[10000000];
int main() {
cout << a[9999999] << endl;
}
int main() {
int n, mod, ans = 1;
cin >> n >> m;
for(int i=0;i<n;i++) ans = (ans * i) % mod;
cout << ans << endl;
}
int pow(int a, int b, int m) { // a^b % m
int ans = 1;
while(b) {
if(b & 1) ans = (ans * a) % m;
a = (a * a) % m, b >>= 1;
}
return ans;
}
二進位拆分 or 分治
int main() {
int n, ans = 0;
cin >> n;
for(int i=0;i<n;i++) ans += i;
cout << ans << '\n';
}
int main() {
int n, ans = 0;
cin >> n;
for(int i=0;i<n;i++) {
for(int j=i;j<n;j++) ans += i*j;
} cout << ans << '\n';
}
int main() {
int n, ans = 0;
cin >> n;
for(int i=1;i<n;i*=2) ans += i;
cout << ans << endl;
}
int main() {
int n, ans = 0;
cin >> n;
ans = n * (n + 1) / 2;
cout << n << '\n';
}
\(10^8\)經驗法則
1. 變數歸零
2. 輸出格式 (要換行)
3. long long
4. 陣列戳到外面(?)
5. 除以零
6. 變數重名 (尤其是迴圈的i, j)
7. \(10^8\)法則
0. 演算法錯誤 (沒救QQ)
每天都在用
int val[9] = {1, 5, 10, 20, 50, 100, 200, 500, 1000};
int cnt[9];
int main() {
int p, ans = 0;
cin >> p;
for(int i=0;i<9;i++) cin >> cnt[i];
for(int i=8;i>=0;i--) {
int take = min(cnt[i], p/val[i]);
ans += take, p -= val[i] * take;
}
cout << ans << '\n';
}
你有三疊牌,每疊牌有\(N\)張
有\(N\)個回合,每個回合要在每一疊牌各拿出一張,三張牌中的最小值的就是得分
求總得分的最大值
\((N \leq 10^5)\)
考慮以下貪心策略:
1. 每次看三堆中最小的牌
2. 拿出最小的那張
3. 剩下兩堆都各拿最大的那張
➝ 就是全部牌的前\(N\)小啦
#include <bits/stdc++.h>
using namespace std;
int a[300010];
signed main() {
int n, ans = 0;
cin >> n;
for(int i=0;i<3*n;i++) cin >> a[i];
sort(a, a + 3*n);
for(int i=0;i<n;i++) ans += a[i];
cout << ans << '\n';
}
最常被拿來當例子的爬樓梯:
狀態、轉移、基底
int dp[100000], n;
int main() {
cin >> n;
dp[0] = dp[1] = 1;
for(int i=2;i<=n;i++) {
dp[i] = dp[i-1] + dp[i-2];
}
cout << dp[n] << '\n';
}
給一個數列,你可以把數字塗成紅、綠、藍三種顏色
但是相鄰的數字不能同顏色
求 (綠色數字和 - 紅色數字和) 的最大值
\((N \leq 10^6)\)
我們先只考慮前\(i\)個數字:
R[i] 是第\(i\)個一定要塗紅色的答案
G[i] 是第\(i\)個一定要塗綠色的答案
B[i] 是第\(i\)個一定要塗藍色的答案
轉移不難想到(?)
#include<bits/stdc++.h>
using namespace std;
int R[1000010], G[1000010], B[1000010], a[1000010];
int main() {
int n;
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) {
R[i] = max(G[i-1], B[i-1]) - a[i];
G[i] = max(R[i-1], B[i-1]) + a[i];
B[i] = max(R[i-1], G[i-1]);
}
cout << max({R[n], G[n], B[n]}) << '\n';
}
猜數字
// find miximum x that ok(x) = true
int main() {
int ub = 1000 + 1, lb = 0;
while(ub - lb > 1) {
int mid = (ub + lb) / 2;
if(ok(mid)) lb = mid;
else ub = mid;
}
cout << lb << '\n';
}
猜數字、猜答案、猜各種有單調性的東西
一堆黑白牛在一個山坡上,第\(i\)個位置的高度是\(a_i\),
你可以任意交換這些牛的順序,使得相鄰兩黑牛的高度差不超過\(K\)
求最小可能的\(K\)
\((N \leq 2\times 10^5)\)
對\(K\)二分搜
驗解採用 \(O(n)\) dp
W[i] = 前\(i\)隻牛,且第\(i\)隻是白牛的最大可能總黑牛數
B[i] = 前\(i\)隻牛,且第\(i\)隻是黑牛的最大可能總黑牛數
因為黑牛一開始在哪根本沒差,所以只要看數量
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, x, black = 0, h[200010], b[200010], w[200010];
bool ok(int mid) {
for(int i=0;i<n;i++) b[i] = w[i] = 0;
b[0] = 1;
for(int i=1;i<n;i++) {
w[i] = max(b[i-1], w[i-1]);
if(abs(h[i] - h[i-1]) <= mid) b[i] = max(b[i-1], w[i-1]) + 1;
else b[i] = w[i-1] + 1;
}
return max(b[n-1], w[n-1]) >= black;
}
signed main() {
cin >> n;
for(int i=0;i<n;i++) cin >> x >> h[i], black += x;
int ub = 2e9 + 1, lb = -1;
while(ub - lb > 1) {
int mid = (ub + lb) / 2;
if(ok(mid)) ub = mid;
else lb = mid;
}
cout << ub << '\n';
}
一個集合
set<int> s; // multiset<int>
int main() {
int n = 8;
s.insert(n);
cout << s.count(5) << '\n';
s.insert(5);
s.erase(8);
cout << s.size() << '\n';
for(auto i: s) cout << i << ' ';
}
給定\(N\)個人來的時間順序,
以及每個人對應行李被送到輸送帶上的時間順序
若輸送帶只能放\(k\)個行李,且最多只能\(p\)個人在輸送帶旁等待行李,求是否能讓所有人都拿到行李
\((N \leq 10^5)\)
貌似很多細節的模擬
先放前\(k\)個行李在輸送帶上
之後讓人依序進來,拿不到行李就繼續等待
;若拿到行李,就直接離開並且補上新的行李
每次補上行李要重新判斷是否有正在等待的人可以拿到行李
#include <bits/stdc++.h>
using namespace std;
int luggage[100010], people[100010];
set<int> online, queued;
signed main() {
int n, k, p, x, maxcnt = 0;
cin >> n >> k >> p;
for(int i=1;i<=n;i++) cin >> x, luggage[x] = i;
for(int i=1;i<=n;i++) cin >> x, people[x] = i;
for(int i=1;i<=k;i++) online.insert(luggage[i]);
int nxt = k + 1;
for(int i=1;i<=n;i++) {
if(online.count(people[i])) {
online.erase(people[i]);
while(queued.count(luggage[nxt])) {
queued.erase(luggage[nxt++]);
}
online.insert(luggage[nxt++]);
}
else queued.insert(people[i]);
maxcnt = max(maxcnt, (int)queued.size());
}
cout << (maxcnt <= p ? "Yes\n" : "No\n");
}
字串 & 映射
map<string, int> s;
int main() {
string n;
cin >> n;
s[n] = 8;
s["abcd"] = s[n] + 1;
cout << s.count("gg") << '\n';
s.insert({"this", 3});
cout << n.size() << ' ' << s.size() << '\n';
for(auto i: s) {
cout << i.first << ' ' << i.second << '\n';
}
}
給\(N\)個字串,若兩字串相同位置有相同字元稱為一個匹配
求這\(N\)個字串的總匹配數
\(N\leq 10^6,\Sigma L_i \leq 5\times 10^6\)
數學題
可以發現不同位置是互相獨立的
而對於同一個位置,若某字元出現\(x\)次,則會使答案多出\(C^x_2\)
#include<bits/stdc++.h>
using namespace std;
map<char, int> cnt[1000010];
int main() {
int n, maxlen = 0;
string s;
cin >> n;
while(n--) {
cin >> s;
for(int i=0;i<s.size();i++) cnt[i][s[i]]++;
maxlen = max(maxlen, (int)s.size());
}
long long ans = 0;
for(int i=0;i<maxlen;i++) {
for(auto c: cnt[i]) {
long long val = c.second;
ans += val * (val - 1) / 2;
}
}
cout << ans << '\n';
}
快速計算多個區間和的好幫手
int a[100001], n, q, l, r;
int main() {
cin >> n >> q;
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=1;i<=n;i++) a[i] += a[i-1];
while(q--) {
cin >> l >> r;
cout << a[r] - a[l-1] << '\n';
}
}
座標平面上有\(N\)個金礦跟\(M\)個銀礦,每個礦都有不同價值
現在選定兩個礦,將以兩礦為對角線的長方形中所有礦物挖起來
不過你最多只能挖\(K\)個金礦
求最大價值總和
\((N+M \leq 5000)\)
枚舉每個礦物的pair
然後用二維前綴和計算這兩個礦物之間
的金礦數量跟總價值
座標範圍很大不好做前綴和
不過實際的坐標是多少其實不重要,只要維持大小順序就好
所以可以先把值域壓縮
void compress(int a[], int now = 0) {
map<int,int> g;
for(int i=0;i<n+m;i++) {
if(!g[a[i]]) g[a[i]] = ++now;
a[i] = g[a[i]];
}
}
void compress(int a[]) {
for(int i=0;i<n+m;i++) tmp[i] = a[i];
sort(a, a+n+m);
int len = unique(a, a+n+m) - a;
for(int i=0;i<n+m;i++) {
tmp[i] = lower_bound(a, a+len, tmp[i]) - a + 1;
}
for(int i=0;i<n+m;i++) a[i] = tmp[i];
}
#include<bits/stdc++.h>
using namespace std;
int n, m, k, x[5010], y[5010], v[5010], tmp[5010], gold[5010][5010];
long long tot[5010][5010];
void compress(int a[], int now = 0) { /* .. */ };
int main() {
long long ans = 0;
cin >> n >> m >> k;
for(int i=0;i<n+m;i++) cin >> x[i] >> y[i] >> v[i];
compress(x), compress(y);
for(int i=0;i<n+m;i++) tot[x[i]][y[i]] = v[i];
for(int i=0;i<n;i++) gold[x[i]][y[i]] = 1;
for(int i=1;i<=n+m;i++) for(int j=1;j<=n+m;j++) {
tot[i][j] += tot[i][j-1] + tot[i-1][j] - tot[i-1][j-1];
gold[i][j] += gold[i][j-1] + gold[i-1][j] - gold[i-1][j-1];
}
for(int i=0;i<n+m;i++) for(int j=i;j<n+m;j++) {
int x1 = min(x[i], x[j]), x2 = max(x[i], x[j]);
int y1 = min(y[i], y[j]), y2 = max(y[i], y[j]);
long long val = tot[x2][y2] - tot[x1-1][y2] - tot[x2][y1-1] + tot[x1-1][y1-1];
int weight = gold[x2][y2] - gold[x1-1][y2] - gold[x2][y1-1] + gold[x1-1][y1-1];
if(weight <= k) ans = max(ans, val);
}
cout << ans << '\n';
}
大家應該都會(?)
int v[1000], w[1000], dp[100000], n, W;
int main() {
cin >> n >> W;
for(int i=0;i<n;i++) cin >> v[i] >> w[i];
for(int i=0;i<n;i++) {
for(int j=W;j>=w[i];j--) {
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
}
}
cout << dp[W] << '\n';
}
⼀組密碼是⼀個只有 0 與 1 組成的字串
⼀組密碼的複雜度是「有⾄少⼀個 1 的⼦區間數量」
有 \(Q\) 個詢問, 每個詢問包含⾮負整數 \(K_i\)
對於每個詢問輸出任意一種複雜度 \(K_i\) 、長度為\(N\)的密碼
若不存在滿⾜條件的密碼,輸出⼀⾏ "No"
\((N \leq 300)\)
「有⾄少⼀個 1 的⼦區間數量」= \(\frac{n(n+1)}{2}\) - 「全是0的連續區間數」
所以複雜度\(K\)的密碼就有\((\frac{n(n+1)}{2} - K)\)個「全是0的連續區間」
若存在長度\(N - 1\)、複雜度\(K\)的密碼,
則存在長度\(N\)、複雜度\(K\)的密碼
當然更長的也都會有,只要一直把 1 串在後面就行
所以我們只要找到最短的解
Note:複雜度\(K\)的密碼有\((\frac{n(n+1)}{2} - K)\)個「全是0的連續區間」
長度為\(L\)的連續0可以提供\(\frac{L(L+1)}{2}\)個「全是0的連續區間」
-> 有無限多個價值\(\frac{L(L+1)}{2}\)的東西,而每個東西的重量是\(L\),目標是求最小重量且價值總和 = \((\frac{n(n+1)}{2} - K)\)
#include<bits/stdc++.h>
using namespace std;
int dp[100000], tr[100000], dif[100000];
int main() {
int n, q, x;
cin >> n >> q;
for(int i=1;i<=n*(n+1)/2;i++) dp[i] = 1e9;
for(int i=1;i<=n;i++) {
for(int j=i*(i+1)/2;j<=n*(n+1)/2;j++) {
if(dp[j-i*(i+1)/2] + i < dp[j]) {
dp[j] = dp[j-i*(i+1)/2] + i;
tr[j] = j-i*(i+1)/2, dif[j] = i;
}
}
}
while(q--) {
cin >> x;
x = n*(n+1)/2 - x;
int cnt = 0;
string ans;
while(x) {
for(int i=0;i<dif[x];i++) ans += '0', cnt++;
if(x = tr[x]) ans += '1', cnt++;
}
while(ans.size() < n) ans += '1';
if(ans.size() == n) cout << ans << '\n';
else cout << "No\n";
}
}
Thanks for your listening
By 林尚廷
10/23 NPSC2019國中組難題選解 @中山國中