林尚廷
No Code No Life :: A competitive programmer
presentation by 絕對沒有抄去年slides的 03t
@線上
程式競賽小技巧們
standard template library (STL)
貪心法 / greedy method
二分搜尋 / binary search
動態規劃 / dynamic programming
前綴和 / prefix sum
離散化 / coordinate compression
How to deal with
#include<stdio>
#define int long long
int main() (
int n, k;
ans = 2k - n;
if(ans > 0) printf("%d\n", ans)
e1se printf("-%d\n", ans);
)
輸入\(n, k\),輸出\(k\)與\((n-k)\)的差值
How to deal with
#include<bits/stdc++.h>
using namespace std;
int main() {
int n, ans;
cin >> n;
for(int i=1; i<=n; i++) {
ans *= i;
}
cout << ans << '\n';
}
計算\(n\)的階乘
How to deal with
#include<bits/stdc++.h>
using namespace std;
int main() {
int n, cnt = 0;
cin >> n;
for(int i=0; i<=n; i++) {
if(n % i == 0) cnt++;
}
cout << cnt << '\n';
}
計算\(n\)的正因數個數
#include<bits/stdc++.h>
using namespace std;
int main() {
int x, n, a, cnt[10000000] = {};
cin >> n >> a;
for(int i=0; i<n; i++) {
cin >> x;
cnt[x]++;
}
cout << cnt[a] << '\n';
}
統計\(n\)個數字中,\(a\)出現幾次
How to deal with
#include<bits/stdc++.h>
using namespace std;
int main() {
int n, sum = 0;
cin >> n;
for(int i=1; i<=n; i++) {
while(i) sum += i % 10, i /= 10;
}
cout << sum << '\n';
}
計算所有小於\(n\)的正整數的各位數和
How to deal with
#include<bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(0), cin.tie(0);
int x, ans = 0;
for(int i=0; i<1000000; i++) {
cin >> x, ans += x;
}
cout << ans << '\n';
}
計算\(10^6\)個數字的總和
How to deal with
電腦每秒鐘約可以跑\(10^8\)次的加法或乘法
例:若題目限制\(n \leq 10^4\),則必須實作兩層迴圈或以下的程式
#include<bits/stdc++.h>
using namespace std;
int main() {
int n, ans = 0;
cin >> n;
for(int i=0; i<n; i++) {
for(int j=0; j<n; j++) ans++;
}
cout << ans << '\n';
}
2016-final pF. 三角桌會議 (extended ver.)
三位武⼠分別會坐在三⾓形其中⼀個邊的中點上。
如果武⼠ \(A\) 與武⼠ \(B\) 距離 \(X\) 則武⼠ \(A\) 與武⼠ \(B\) 溝通的困難度則爲 \(2X\) 。
整個三⾓桌會議溝通的困難度即爲兩兩溝通困難度的和。
三⾓王想知道給定⼀張三⾓桌下,三⾓桌會議溝通的困難度是多少。
\(A\)
\(B\)
\(C\)
Input |
---|
三個整數 A, B, C,代表三⾓桌的三個邊⻑。 |
Output |
輸出⼀⾏,包含⼀個整數,代表三⾓桌會議溝通的困難程度。 |
\(A, B, C \leq 5 \times 10^{18}\)
Sample Input 2 | Sample Output 2 |
---|---|
3242342342567543456 3242342345675432456 23843788 | 6484684688266819700 |
#include<bits/stdc++.h>
using namespace std;
int main() {
unsigned long long a, b, c;
cin >> a >> b >> c;
cout << a + b + c << '\n';
}
8 bytes 整數 (long long)
signed:\(-2^{63} \leq x < 2^{63}\)
unsigned:\(0 \leq x < 2^{64}\)
modular arithmetic
modular exponent
輸入\(a, b, m\)三個整數,輸出\(a^b\)除以\(m\)的餘數。
\(1 \leq a, b \leq 10^6,m\leq 10^4\)
Sample Input 1 | Sample Output 1 |
---|---|
2 4 3 | 1 |
Sample Input 2 | Sample Output 2 |
3 1000000 7 | 4 |
int main() {
int a, b, m, ans = 1;
cin >> a >> b >> m;
for(int i=0;i<b;i++) {
ans = (ans * a) % mod;
}
cout << ans << endl;
}
big modular exponent (extended)
輸入\(a, b, m\)三個整數,輸出\(a^b\)除以\(m\)的餘數。
\(1 \leq a, b \leq 10^9,m \leq 10^4\)
Sample Input 1 | Sample Output 1 |
---|---|
2 4 3 | 1 |
Sample Input 2 | Sample Output 2 |
3 1000000000 13 | 3 |
int mpow(int a, int b, int m) {
int ans = (mpow(a/2) * mpow(a/2)) % m;
if(a % 2) ans = (ans * a) % m;
return ans;
}
俗稱:快速冪
組合數
輸入\(n, k, m\)三個整數,輸出\(C^n_k\)除以\(29947\)的餘數。
\(1 \leq k < n \leq 10^6\)
Sample Input 1 | Sample Output 1 |
---|---|
5 2 | 10 |
Sample Input 2 | Sample Output 2 |
28473 14421 | 6988 |
2016-semi pC. 保羅的寶⾙
\(1 ≤ N ≤ M ≤ 10^6\)
\(1 ≤ 每個寶⾙的重量 ≤ 10^4\)
\(1 ≤ 每個櫃⼦的距離 ≤ 10^4\)
Sample Input 1 | Sample Output 1 |
---|---|
5 6 10 2 1 514 4 1 2 100 2 3 9 |
557 |
Sample Input 2 | Sample Output 2 |
7 7 4 1 82 3 43 12 1 2 45 12 63 6 12 6 3 |
686 |
最佳策略是什麼?
#include<bits/stdc++.h>
using namespace std;
int w[1000010], d[1000010];
int main() {
long long ans = 0;
int n, m;
cin >> n >> m;
for(int i=0; i<n; i++) cin >> w[i];
for(int i=0; i<m; i++) cin >> d[i];
sort(w, w + n, greater<int>());
sort(d, d + m);
for(int i=0;i<n;i++) ans += w[i] * d[i];
cout << ans << '\n';
}
2016-final pA. 大富翁
你想寫個程式算算看當擲出 \(n\) 顆骰⼦時,有幾種可能的點數組合會使得點數總和為 \(m\)。
在本題中我們使⽤的骰⼦都是最常⾒的六⾯骰,上⾯分別有⼀到六點。
舉例來說,3 顆骰⼦擲出點數總和為 5 的可能組合共有六種:113, 122, 131, 212, 221, 311。
\(1 ≤ n ≤ 5\)
\(n ≤ m ≤ 6n\)
Sample Input 1 | Sample Output 1 |
---|---|
2 5 | 4 |
Sample Input 2 | Sample Output 2 |
3 5 | 6 |
#include<stdio.h>
int main() {
int n, m, a, b, c, d, e, ans = 0;
scanf("%d%d", &n, &m);
if(n == 1) {
for(a=1; a<=6; a++) {
if(a == m) ans++;
}
}
if(n == 2) {
for(a=1; a<=6; a++) {
for(b=1; b<=6; b++) {
if(a+b == m) ans++;
}
}
}
if(n == 3) {
... (3 nested loops)
}
if(n == 4) {
... (4 nested loops)
}
if(n == 5) {
... (5 nested loops)
}
printf("%d\n", ans);
}
Accepted but...
if \(n=3, m=10\)
= (2, 9)
= (2, 7)
= (2, 5)
= (2, 8)
= (2, 6)
= (2, 4)
recursion (遞迴)
int solve(int n, int m) {
if(n == 0) return (m ? 0 : 1);
int ans = 0;
for(int i=1; i<=6; i++) {
ans += solve(n-1, m-i);
}
return ans;
}
先別管這個了,你聽過安麗嗎? (extended)
給你一堆字串,若該字串是第一次出現,就回答"NO"。若該字串曾經出現過,則回答"YES"。
每一行不超過 \(100\) 個字元,最多 \(10^5\) 行。
Sample Input 1 | Sample Output 1 |
---|---|
ann lee jackyliuxx ann lee The cake is a lie |
NO NO YES NO |
Sample Input 2 | Sample Output 2 |
hand in hand 39 music hand in hand love trial hand in hand hibana 39 music |
NO NO YES NO YES NO YES |
一個集合
#include<bits/stdc++.h>
using namespace std;
set<string> s;
signed main() {
string str;
while(getline(cin, str)) {
if(s.count(str)) cout << "YES\n";
else cout << "NO\n";
s.insert(str);
}
}
給你一堆字串,若該字串是第一次出現,就回答"NO"。若該字串曾經出現過,則回答它上一次出現在第幾行。
每一行不超過 \(100\) 個字元,最多 \(10^5\) 行。
Sample Input 1 | Sample Output 1 |
---|---|
ann lee jackyliuxx ann lee The cake is a lie |
NO NO 1 NO |
Sample Input 2 | Sample Output 2 |
hand in hand 39 music hand in hand love trial hand in hand hibana 39 music |
NO NO 1 NO 3 NO 2 |
映射
#include<bits/stdc++.h>
using namespace std;
map<string, int> s;
signed main() {
string str;
int cnt = 0;
while(getline(cin, str)) {
if(s.count(str)) cout << s[str] << endl;
else cout << "NO\n";
s[str] = ++cnt;
}
}
by 絕對沒有抄去年slides的 03T
快速冪 / binary exponentiation
同餘 / modular arithmetic
排序 / sorting
枚舉法 / enumerating
遞迴 / recursion
std::set & std::map
貪心法 / greedy method
二分搜尋 / binary search
動態規劃 / dynamic programming
枚舉法 / enumerating
圖 / graph (應該不會出 吧)
貪心法
今朝有酒今朝醉,明日愁來明日愁
貪心法
顧名思義,就是貪心
e.g. 捷徑、比價
例題:買東西
Sample Input 1 | Sample Output 1 |
---|---|
73 4 3 10 1 0 0 0 0 1 |
9 |
你想要買一個\(N\)元的東西
你錢包裡各種面額的錢都是有限個
輸出你最少需要拿出多少個硬幣/紙鈔才能剛好付清
#include<bits/stdc++.h>
using namespace std;
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)\)
Sample Input 1 | Sample Output 1 |
---|---|
3 1 1 1 2 2 2 3 3 3 |
3 |
Sample Input 2 | Sample Output 2 |
2 1 3 4 2 5 6 |
3 |
考慮以下貪心策略:
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';
}
動態規劃
狀態、轉移、基底
動態規劃
DP = 遞迴 + 記錄重複答案
e.g. 上次講的大富翁(dice)
例題:費事數列 (Hofstadter sequence)
Sample Input 1 | Sample Output 1 |
---|---|
12 | 8 7 |
輸入\(n\),求\(F\)與\(M\)的第\(n\)項,若:
\(n \leq 10^5\)
#include<bits/stdc++.h>
using namespace std;
int F[100010], M[100010];
int main() {
int n;
cin >> n;
F[0] = 1, M[0] = 0;
for(int i=1; i<=n; i++) {
F[i] = i - M[F[i-1]];
M[i] = i - F[M[i-1]];
}
cout << F[n] << ' ' << M[n] << endl;
}
#include<bits/stdc++.h>
using namespace std;
int f(int n);
int m(int n);
int F[100010], M[100010];
int f(int n) {
if(F[n] != 0) return F[n];
if(n == 0) return 1;
return F[n] = n - m(f(n-1));
}
int m(int n) {
if(M[n] != 0) return M[n];
if(n == 0) return 0;
return M[n] = n - f(m(n-1));
}
signed main() {
int n;
cin >> n;
cout << f(n) << ' ' << m(n) << '\n';
}
bb 與序列
給一個數列,你可以把數字塗成紅、綠、藍三種顏色
但是相鄰的數字不能同顏色
求 (綠色數字和 - 紅色數字和) 的最大值
\((N \leq 10^6)\)
Sample Input 1 | Sample Output 1 |
---|---|
2 10 -10 | 20 |
Sample Input 2 | Sample Output 2 |
3 1 2 3 |
4 |
Sample Input 3 | Sample Output 3 |
7 4 -6 10 3 -10 -1 5 |
35 |
4 -6 10 3 -10 -1 5
思考3:
4 -6 10 = ?
4 -6 10 = ?
4 -6 10 = ?
4 -6 10 = ?
思考4:
4 -6 10 3 -10 -1 5 = ?
4 -6 10 3 -10 -1 5 = ?
4 -6 10 3 -10 -1 5 = ?
4 -6 10 3 -10 -1 5 = ?
4 = ?
思考1:
4 = ?
4 = ?
4 = ?
->
4 -6 = ?
思考2:
4 -6 = ?
4 -6 = ?
4 -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';
}
二分搜尋法
剖半剖半再剖半
拿來猜東西很快
e.g. 猜數字、猜答案、猜各種有單調性的東西
二分搜尋法
例題:猜數字
現在有一個未知函數\(ok(x)\),它在\(x\leq K\)時會回傳true
保證\(0<K\leq 1000\)
現在你要呼叫它10次以內,猜出\(K\)是多少
#include "guess.h"
#include "guess.h"
#include<bits/stdc++.h>
using namespace std;
// 找到最大 x 使得 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\)的最小值\(K_{min}\) \((N \leq 2\times 10^5,-10^9 < a_i \leq 10^9)\)
Sample Input 1 | Sample Output 1 |
---|---|
5 1 1 1 2 0 3 1 4 1 5 |
1 |
轉化問題:
若耐壓上限為\(x\)時存在一種排列方式,則我們稱\(ok(x)\) = true
觀察到若\(ok(x)\) = true,則任何比\(x\)大的數也會是true
也就是說,\(ok(x)\)在\(x\geq K_{min}\)時會回傳true
於是,若我們有\(ok(x)\),則可以用二分搜快速猜出\(K_{min}\)
#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) { /* if it can be called */ }
signed main() {
cin >> n;
for(int i=0;i<n;i++) cin >> x >> h[i];
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';
}
因為答案只與黑牛的數量有關,與初始位置無關
所以\(ok(x)\) 可以採用 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';
}
Thanks for your listening
一個集合
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";
}
}
By 林尚廷
11/07 & 14 程式競賽技巧+NPSC國中組選解 @線上