11/07

程式競賽小技巧 + NPSC 2016 選解

presentation by  絕對沒有抄去年slides的 03t

@線上

index

  • 程式競賽小技巧們

  • standard template library (STL)

  • 貪心法 / greedy method

  • 二分搜尋 / binary search

  • 動態規劃 / dynamic programming

  • 前綴和 / prefix sum

  • 離散化 / coordinate compression

Bugs Everywhere

Compile Error

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);
)

2017-final pF 水題

輸入\(n, k\),輸出\(k\)與\((n-k)\)的差值

Wrong Answer

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\)的階乘

Runtime Error

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\)出現幾次 

Runtime Error

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\)的正整數的各位數和

Time Limit Exceeded

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\)個數字的總和

Time Limit Exceeded

How to deal with

\(10^8\)/s Rule

  • 電腦每秒鐘約可以跑\(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';
}

Problem 1

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

Solution

#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}\)

Problem 2

modular arithmetic

Problem 2.1

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

Modular Arithmetic

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;
}
(a + b) \mod M = (a \mod M + b \mod M) \mod M \\ (a - b) \mod M = (a \mod M - b \mod M) \mod M \\ (a \times b) \mod M = (a \mod M \times b \mod M) \mod M \\ (a \div b) \mod P = (a \mod P \times b^{P-2} \mod P) \mod P

Problem 2.2

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;
}
a^b = (a^{ \frac{b}{2} }) ^ 2

Binary Exponentiation

俗稱:快速冪

Problem 2.71828

組合數

輸入\(n, k, m\)三個整數,輸出\(C^n_k\)除以\(29947\)的餘數。

\(1 \leq k < n \leq 10^6\)

C^n_k = \frac{n!}{k!(n-k)!}
Sample Input 1 Sample Output 1
5  2 10
Sample Input 2 Sample Output 2
28473  14421 6988

Problem 3

2016-semi pC. 保羅的寶⾙

  • 保羅有 \(N\) 個寶⾙放在桌⼦上,以及 \(M\) 個拿來保存寶⾙的櫃⼦,其中每個櫃⼦最多只能裝⼀個寶⾙
  • 搬動寶貝的疲勞程度是每個寶⾙重量乘上搬運該寶⾙的距離的總和。
  • 保羅每次只能搬⼀個寶⾙,搬完就得要回到桌⼦搬下⼀個寶⾙。此外,沒有搬運寶⾙時任何⾛動都不會累積疲勞程度。
  • 保羅已經知道 \(N\) 個寶⾙的重量與 \(M\) 個存放寶⾙的櫃⼦離桌⼦的距離,他想知道他搬運寶⾙疲勞程度最⼩是多少?

\(1 ≤ N ≤ M ≤ 10^6\)

\(1 ≤ 每個寶⾙的重量 ≤ 10^4\)

\(1 ≤ 每個櫃⼦的距離 ≤ 10^4\)

Discuss and Ponder 

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

最佳策略是什麼?

Solution

#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';
}

Problem 4

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

What did I do? 

#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...

What you should think about ?

if \(n=3, m=10\)

= (2, 9)

= (2, 7)

= (2, 5)

= (2, 8)

= (2, 6)

= (2, 4)

Solution

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;
}

Problem 5

先別管這個了,你聽過安麗嗎? (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

std::set

一個集合

#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

std::map

映射

#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;
    }
}

Puzzles

Think About It

Mind Blowing

休息是為了

                  不要走路

11/14

NPSC 2019 難題選解

by  絕對沒有抄去年slides的 03T

review

  • 快速冪 / binary exponentiation

  • 同餘 / modular arithmetic

  • 排序 / sorting

  • 枚舉法 / enumerating

  • 遞迴 / recursion

  • std::set & std::map

index

  • 貪心法 / greedy method

  • 二分搜尋 / binary search

  • 動態規劃 / dynamic programming

  • 枚舉法 / enumerating

  • 圖 / graph (應該不會出 吧)

Greedy Method

貪心法

 

今朝有酒今朝醉,明日愁來明日愁

Greedy Method

貪心法

顧名思義,就是貪心

 

e.g. 捷徑、比價

Greedy Method

例題:買東西

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';
}

Problem A

貓貓卡牌

  • 你有三疊牌,每疊牌有\(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

Solution

考慮以下貪心策略:

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';
}

Dynamic Programming

動態規劃

 

狀態、轉移、基底

Dynamic Programming

動態規劃

DP = 遞迴 + 記錄重複答案

 

e.g. 上次講的大富翁(dice)

Dynamic Programming

例題:費事數列 (Hofstadter sequence)

Sample Input 1 Sample Output 1
12 8  7
F_0 = 1 \\ M_0 = 0 \\ F_i = i - M_{F_{i - 1}} \\ M_i = i - F_{M_{i - 1}}

輸入\(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';
}

Problem F

 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 = ?

->

Solution

我們先只考慮前\(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';
}

Binary Search

二分搜尋法

 

剖半剖半再剖半

Binary Search

拿來猜東西很快

 

e.g. 猜數字、猜答案、猜各種有單調性的東西

二分搜尋法

Binary Search

例題:猜數字

  • 現在有一個未知函數\(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';
}

Problem D

魔法學院與咖啡山

  • 一堆黑白牛在一個山坡上,第\(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';
}

Solution

因為答案只與黑牛的數量有關,與初始位置無關

所以\(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

➣ std::set

一個集合

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 << ' ';
}

Problem B

給定\(N\)個人來的時間順序,

以及每個人對應行李被送到輸送帶上的時間順序

 

若輸送帶只能放\(k\)個行李,且最多只能\(p\)個人在輸送帶旁等待行李,求是否能讓所有人都拿到行李

 

\((N \leq 10^5)\)

Solution

貌似很多細節的模擬

 

先放前\(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");
}

➣ std::string & map

字串 & 映射

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';
    }
}

Problem C

給\(N\)個字串,若兩字串相同位置有相同字元稱為一個匹配

 

求這\(N\)個字串的總匹配數

 

\(N\leq 10^6,\Sigma L_i \leq 5\times 10^6\)

Solution

數學題

 

可以發現不同位置是互相獨立的

而對於同一個位置,若某字元出現\(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';
    }
}

Problem G

座標平面上有\(N\)個金礦跟\(M\)個銀礦,每個礦都有不同價值

現在選定兩個礦,將以兩礦為對角線的長方形中所有礦物挖起來

 

不過你最多只能挖\(K\)個金礦

求最大價值總和

\((N+M \leq 5000)\)

Solution

枚舉每個礦物的pair

 

然後用二維前綴和計算這兩個礦物之間

的金礦數量跟總價值

 

座標範圍很大不好做前綴和

不過實際的坐標是多少其實不重要,只要維持大小順序就好

所以可以先把值域壓縮

➣ 二維前綴和

s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1]
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';
}

➣ 0/1背包

大家應該都會(?)

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';
}

Problem F

⼀組密碼是⼀個只有 0 與 1 組成的字串

⼀組密碼的複雜度是「有⾄少⼀個 1 的⼦區間數量」

 

有 \(Q\) 個詢問, 每個詢問包含⾮負整數 \(K_i\)

對於每個詢問輸出任意一種複雜度 \(K_i\) 、長度為\(N\)的密碼

若不存在滿⾜條件的密碼,輸出⼀⾏ "No" 

\((N \leq 300)\)

性質觀察1

「有⾄少⼀個 1 的⼦區間數量」= \(\frac{n(n+1)}{2}\) - 「全是0的連續區間數」

 

所以複雜度\(K\)的密碼就有\((\frac{n(n+1)}{2} -  K)\)個「全是0的連續區間」

性質觀察2

若存在長度\(N - 1\)、複雜度\(K\)的密碼,

存在長度\(N\)、複雜度\(K\)的密碼

 

當然更長的也都會有,只要一直把 1 串在後面就行

所以我們只要找到最短的解

Solution

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";
    }
}

11/07 NPSC'21

By 林尚廷

11/07 NPSC'21

11/07 & 14 程式競賽技巧+NPSC國中組選解 @線上

  • 498