低批

複習01背包

扣的

#include <bits/stdc++.h>
using namespace std;
array<int, 104> A;
array<int, 100004> dp;
bool bag(int n, int c){
    for(int j = 0; j < n; j++){
        for(int i = c; i >= A[j]; i--){
            dp[i] = max(dp[i], dp[i - A[j]] + A[j]);
        }
    }
    return dp[c] == c;
}
signed main(){
    int m, n, sum;
    cin >> m >> n;
    while(m--){
        sum = 0;
        for(int &d : dp) d = 0;
        for(int i = 0; i < n; i++){
            cin >> A[i];
            sum += A[i];
        }
        if(sum & 1) cout << "No\n";
        else cout << (bag(n, sum / 2)? "Yes\n" : "No\n");
    }
    return 0;
}

無限背包

每個物品數量無限

狀態

dp(i, j) = i \ weight, first \ j \ items, max \ value

有限背包轉移式

dp(i, j) = max(dp(i, j - 1), dp(i - w_j, j - 1) + v_j)

無限背包轉移式

dp(i, j) = max(dp(i, j - 1), dp(i - w_j, j) + v_j)

複雜度?

狀態數 : 

轉移時間 : 

O(NC)
O(1)
O(NC)

扣的

array<int, 1004> W, V;
array<array<int, 1004>, 1004> dp;
int infbag(int n, int c){
    int ans = 0;
    for(int j = 0; j < n; j++){
        for(int i = W[j]; i <= c; i++){
            dp[i][j] = max(dp[i][j - 1], dp[i - W[j]][j] + V[j]);
            ans = max(ans, dp[i][j]);
        }
    }
    return ans;
}

記憶體比較小的扣的

array<int, 1004> W, V, dp;
int infbag(int n, int c){
    int ans = 0;
    for(int j = 0; j < n; j++){
        for(int i = W[j]; i <= c; i++){
            dp[i] = max(dp[i], dp[i - W[j]] + V[j]);
            ans = max(ans, dp[i]);
        }
    }
    return ans;
}

裸題

扣的

#include <bits/stdc++.h>
using namespace std;
array<int, 1004> A, D;
array<int, 10004> dp;
bool infbag(int w, int b, int n){
    for(int j = 0; j < n; j++){
        for(int i = A[j]; i < w; i++){
            dp[i] = max(dp[i], dp[i - A[j]] + D[j]);
            if(dp[i] >= b) return 1;
        }
    }
    return 0;
}
signed main(){
    int t, w, b, n, m;
    cin >> t;
    while(t--){
        cin >> w >> b >> n;
        for(int &d : dp) d = 0;
        for(int i = 0; i < n; i++){
            cin >> A[i] >> D[i];
        }
        cin >> m;
        for(int i = 0; i < n; i++){
            A[i] += m;
        }
        cout << (infbag(w, b, n)? "Yes\n" : "No\n");
    }
    return 0;
}

湊錢問題

有n種幣值,求湊出k元的最小數量

狀態

dp_i = min \ coin \ to \ i \ dollars

轉移

dp_i = min_{0 \le j < n}(dp_{i - v_j} + 1)

複雜度

狀態數 : 

轉移時間 : 

O(K)
O(N)
O(NK)

扣的

array<int, 1004> V, dp;
int money(int n, int k){
    for(int i = 1; i <= k; i++){
        dp[i] = 1e0;
        for(int j = 0; j < n; j++){
            if(V[j] < i) continue;
            dp[i] = min(dp[i], dp[i - V[j]] + 1);
        }
    }
    return dp[k];
}

不打馬賽克的裸題

扣的

#include <iostream>
#include <algorithm>
using namespace std;
int V[6] = {1, 5, 10, 12, 16, 20};
int dp[104];
int money(int k){
    for(int i = 1; i <= k; i++){
            dp[i] = 1e9;
        for(int j = 0; j < 6; j++){
            if(i - V[j] < 0) break;
            dp[i] = min(dp[i], dp[i - V[j]] + 1);
        }
    }
    return dp[k];
}
signed main(){
    int n;
    cin >> n;
    cout << money(n);
    return 0;
}

分數背包

物品可以被切割

哈哈其實是greedy啦

有限背包

每個物品都有限制數量

陽春之術

拆成01背包做

O(CNK)

扣的

#include <bits/stdc++.h>
using namespace std;
array<int, 54> W, M, C;
array<int, 10004> dp;
int bag(int n, int c){
    int ans = 0;
    for(int j = 0; j < n; j++){
        for(int k = 0; k < C[j]; k++){
            for(int i = c; i >= W[j]; i--){
                dp[i] = max(dp[i], dp[i - W[j]] + M[j]);
                ans = max(ans, dp[i]);
            }
        }
    }
    return ans;
}
signed main(){
    int n, t;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> W[i] >> M[i] >> C[i];
    }
    cin >> t;
    cout << bag(n, t);
    return 0;
}

分塊之術

1, 2, 4, 8, 16 \dots, 2^{h - 1}, K - 2^h + 1 \ (2^h \le K)
O(CNlogK)

扣的

#include <bits/stdc++.h>
using namespace std;
array<int, 100004> W, M, C;
array<int, 1000004> dp;
int bag(int n, int c){
    int ans = 0;
    for(int j = 0; j < n; j++){
        for(int k = 1; k <= C[j]; k *= 2){
            for(int i = c; i >= k * W[j]; i--){
                dp[i] = max(dp[i], dp[i - k * W[j]] + k * M[j]);
                ans = max(ans, dp[i]);
            }
            C[j] -= k;
        }
        int k = C[j];
        for(int i = c; i >= k * W[j]; i--){
            dp[i] = max(dp[i], dp[i - k * W[j]] + k * M[j]);
            ans = max(ans, dp[i]);
        }
    }
    return ans;
}
signed main(){
    int n, t;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> W[i] >> M[i] >> C[i];
    }
    cin >> t;
    cout << bag(n, t);
    return 0;
}

另一種湊錢問題

給你n個不同幣值的錢,每一種都有數量,問能不能湊出c元

對每種錢幣,從小於幣值的每個數開始,每次增加幣值,然後捨去超過數量的

狀態

dp(i, j) = i \ dollar, first \ j \ coins, can?

轉移

dp(i, j) = dp(i - v_jk_j, j - 1) | dp(i - v_j(k_j - 1), j - 1) |\\ \dots | dp(i, j - 1)

複雜度

狀態 : 

轉移 : 

O(NM)
O(1)
O(NM)

扣的

#include <bits/stdc++.h>
using namespace std;
array<int, 104> C, K;
array<array<int, 2>, 20004> dp;
int money(int n, int c){
    for(int j = 0; j < n; j++){
        for(int k = 0; k < C[j]; k++){
            for(int i = k, l = k - C[j] * (K[j] + 1), sum = 0; i <= c; i += C[j], l += C[j]){
                if(l >= 0) sum -= dp[l][1 ^ (j & 1)];
                sum += dp[i][1 ^ (j & 1)];
                dp[i][j & 1] = !!sum;
            }
        }
    }
    return dp[c][1 ^ (n & 1)];
}
signed main(){
    int t, n, m;
    cin >> t;
    while(t--){
        for(int i = 0; i <= m; i++){
            for(int j : {0, 1}){
                dp[i][j] = 0;
            }
        }
        dp[0][0] = dp[0][1] = 1;
        cin >> n >> m;
        for(int i = 0; i < n; i++){
            cin >> C[i] >> K[i];
        }
        cout << (money(n, m)? "Yes\n" : "No\n");
    }
    return 0;
}

LIS

Longest Increasing Subsequence

狀態

dp_i = LIS \ while \ choosing \ A_i \ as \ last \ one

轉移

dp_i = max_{j < i, A_j < A_i}(dp_j + 1)

複雜度

狀態數 : 

轉移時間 : 

O(N)
O(N)
O(N^2)

扣的

array<int, 1004> dp, A;
int LIS(int n){
    int ans = 0;
    for(int i = 0; i < n; i++){
        dp[i] = 1;
        for(int j = 0; j < i; j++){
            if(A[i] > A[j]){
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        ans = max(ans, dp[i]);
    }
    return ans;
}

優化

找到目前LIS中第一個比自己大的,然後取代他

LIS為遞增序列,因此可以二分搜

複雜度

搜尋時間 * 操作次數

O(NlogN)

扣的

#define pb push_back
vector<int> lis;
array<int, 100004> A;
int LIS(int n){
    for(int i = 0; i < n; i++){
        auto p = lower_bound(lis.begin(), lis.end(), A[i]);
        if(p == lis.end()) lis.pb(A[i]);
        else *p = A[i];
    }
    return lis.size();
}

題目

扣的

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
vector<int> lis;
array<int, 100004> A;
int LIS(int n){
    for(int i = 0; i < n; i++){
        auto p = lower_bound(lis.begin(), lis.end(), A[i]);
        if(p == lis.end()) lis.pb(A[i]);
        else *p = A[i];
    }
    return lis.size();
}
signed main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> A[i];
    }
    cout << LIS(n);
    return 0;
}

扣的

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
struct doll{
    int h, w;
};
array<doll, 20004> D;
vector<int> lis;
bool cmp(doll a, doll b){
    if(a.h != b.h) return a.h < b.h;
    return a.w > b.w;
}
int LIS(int n){
    lis.clear();
    for(int i = 0; i < n; i++){
        auto p = lower_bound(lis.begin(), lis.end(), D[i].w);
        if(p == lis.end()) lis.pb(D[i].w);
        else *p = D[i].w;
    }
    return lis.size();
}
signed main(){
    int t, n;
    cin >> t;
    while(t--){
        cin >> n;
        for(int i = 0; i < n; i++){
            cin >> D[i].h >> D[i].w;
        }
        sort(D.begin(), D.begin() + n, cmp);
        cout << LIS(n) << "\n";
    }
    return 0;
}

扣的

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
struct dot{
    int x, y;
};
array<dot, 200004> D;
vector<int> lis;
bool cmp(dot a, dot b){
    if(a.x != b.x) return a.x < b.x;
    return a.y < b.y;
}
int LIS(int n){
    lis.clear();
    for(int i = 0; i < n; i++){
        auto p = upper_bound(lis.begin(), lis.end(), D[i].y);
        if(p == lis.end()) lis.pb(D[i].y);
        else *p = D[i].y;
    }
    return lis.size();
}
signed main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> D[i].x >> D[i].y;
    }
    sort(D.begin(), D.begin() + n, cmp);
    cout << LIS(n);
    return 0;
}

扣的

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
map<int, int> P;
array<int, 200004> A, B, D, L, lis;
array<vector<int>, 200004> S;
int lowerbound(int x){
    int p = 0;
    for(int i = 17; i >= 0; i--){
        if(p + (1 << i) < lis.size() && lis[p + (1 << i)] > x){
            p += (1 << i);
        }
    }
    return p + 1;
}
void LIS(int n){
    for(int i = n; i > 0; i--){
        int p = lowerbound(D[i]);
        lis[p] = D[i];
        L[i] = p;
    }
}
void print(int n, int now){
    if(!n) return;
    int nxt;
    for(int s : S[n]){
        if(s < now) break;
        if(D[s] > D[now] && A[s] > A[nxt]) nxt = s;
    }
    cout << A[nxt] << " ";
    print(n - 1, nxt);
}
signed main(){
    int n, mx = 0;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> A[i];
        P[A[i]] = i;
    }
    for(int i = 1; i <= n; i++){
        cin >> B[i];
        D[P[B[i]]] = i;
    }
    LIS(n);
    for(int i = n; i > 0; i--){
        S[L[i]].pb(i);
        mx = max(mx, L[i]);
    }
    print(mx, 0);
    return 0;
}

編輯距離

送你兩個字串,有三種操作 : 刪掉一個字,加入一個字,更改一個字。求把兩個字串變一樣的最少操作次數

狀態

dp(i, j) = min \ operations \ when \\ first \ i \ char \ on \ first \\ string \ and \ first \ j \\ char \ on \ second \ string

轉移

dp(i, j) = dp(i - 1, j - 1), if(S_{1i} == S_{2j})\\ dp(i, j) = min(dp(i, j - 1), dp(i - 1. j), dp(i - 1, j - 1)) + 1

複雜度

狀態數 : 

轉移時間 : 

O(S_1sizeS_2size)
O(1)
O(S_1sizeS_2size)

題目

扣的

#include <bits/stdc++.h>
using namespace std;
string A, B;
array<array<int, 1004>, 1004> dp;
int ED(int i, int j){
    if(i == 0 || j == 0) return i? i : j;
    if(dp[i][j]) return dp[i][j];
    if(A[i - 1] == B[j - 1]) return dp[i][j] = ED(i - 1, j - 1);
    return dp[i][j] = min({ED(i - 1, j), ED(i, j - 1), ED(i - 1, j - 1)}) + 1;
}
signed main(){
    getline(cin, A);
    getline(cin, B);
    cout << ED(A.size(), B.size());
    return 0;
}

位元低批

位元

二進位的位數

1 0 0 0 0

16

| |

位元運算

~ not 變反的
& and
| or
^ xor 相加不進位
<< left shift 左移
>> right shift 右移

位元枚舉

直接看題目

對每個位置枚舉0 1,

然後算答案

複雜度

狀態數 : 

算答案 : 

O(NK2^N)
O(2^N)
O(NK)

扣的

#include <bits/stdc++.h>
using namespace std;
array<array<int, 16>, 3> S;
void run(int n, int k){
    array<int, 3> dif;
    int now, ans, sml, big = 0;
    for(int i = 0; i < 1 << n; i++){
        sml= 1e9;
        for(int l = 0; l < k; l++){
            dif[l] = 0;
        }
        for(int j = 0; j < n; j++){
            now = (i >> j) & 1;
            for(int l = 0; l < k; l++){
                if(S[l][j] != now) dif[l]++;
            }
        }
        for(int l = 0; l < k; l++){
            sml = min(sml, dif[l]);
        }
        if(sml > big){
            big = sml;
            ans = i;
        }
    }
    for(int j = 0; j < n; j++){
        cout << ((ans >> j) & 1);
    }
}
signed main(){
    int n, k;
    char c;
    cin >> n >> k;
    if(n > 15) return 0;
    for(int i = 0; i < k; i++){
        for(int j = 0; j < n; j++){
            cin >> c;
            S[i][j] = (c ^ '0');
        }
    }
    run(n, k);
    return 0;
}

好土低批

直接看題目

狀態

dp(i, j) = min \ time \ to \ finish \ at \ j \ in\ set \ i

轉移

dp(i, j) = min_{k \subset i}(dp(i - 2^j, k) + wait(k, j))

複雜度

狀態 : 

轉移 : 

O(N2^N)
O(N)
O(N^22^N)

扣的

#include <bits/stdc++.h>
using namespace std;
array<array<int, 16>, 1 << 16> dp;
array<int, 16> T;
int up(int t, int x){
    if(t % x == 0) return t;
    return x * (t / x + 1);
}
int cost(int t, int i, int j){
    t += abs(j - i);
    t = up(t, T[j]);
    return t;
}
int DP(int n){
    int ans = 1e9;
    for(int i = 1; i < 1 << n; i++){
        for(int j = 0; j < n; j++){
            if(!(i & (1 << j))) continue;
            if(i ^ (1 << j)) dp[i][j] = 1e9;
            for(int k = 0; k < n; k++){
                if(k == j || !(i & (1 << k))) continue;
                dp[i][j] = min(dp[i][j], cost(dp[i ^ (1 << j)][k], k, j));
            }
        }
    }
    for(int i = 0; i < n; i++){
        ans = min(ans, dp[(1 << n) - 1][i]);
    }
    return ans;
}
signed main(){
    int n;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> T[i];
        dp[1 << i][i] = T[i];
    }
    cout << DP(n);
    return 0;
}

題目

扣的

#include <bits/stdc++.h>
using namespace std;
array<array<int, 15>, 15> A, V;
array<int, 1 << 15> dp;
int DP(int n){
    int ans = 0, t;
    for(int s = (1 << n) - 1; s >= 0; s--){
        for(int i = 0; i < n; i++){
            if(!(s & (1 << i))) continue;
            for(int j = i + 1; j < n; j++){
                if(!(s & (1 << j))) continue;
                t = s ^ (1 << i) ^ (1 << j) ^ (1 << A[i][j]);
                dp[t] = max(dp[t], dp[s] + V[i][j]);
            }
        }
    }
    for(int i = 1; i < (1 << n); i <<= 1){
        ans = max(ans, dp[i]);
    }
    return ans;
}
signed main(){
    int n;
    while(cin >> n){
        for(int &d : dp) d = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                cin >> V[i][j];
            }
        }
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                cin >> A[i][j];
            }
        }
        cout << DP(n) << "\n";
    }
    return 0;
}

本提極其考驗壓常技巧

扣的

#include <bits/stdc++.h>
using namespace std;
int n;
array<int, 1 << 21> dp;
array<array<int, 21>, 21> R;
int lownotbit(int x){
    for(int i = 0; i < n; i++){
        if(!(x & (1 << i))) return i;
    }
}
int DP(){
    int i, t;
    for(int s = 0; s < (1 << n); s++){
        if(dp[s] == 1e9) continue;
        i = lownotbit(s);
        for(int j = i + 1; j < n; j++){
            if(s & (1 << j)) continue;
            for(int k = j + 1; k < n; k++){
                if(s & (1 << k)) continue;
                t = s | (1 << i) | (1 << j) | (1 << k);
                dp[t] = min(dp[t], dp[s] + R[i][j] + R[j][k] + R[i][k]);
            }
        }
    }
    return dp[(1 << n) - 1];
}
signed main(){
    int t;
    cin >> t;
    while(t--){
        cin >> n;
        for(int i = 1; i < (1 << n); i++) dp[i] = 1e9;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                cin >> R[i][j];
            }
        }
        cout << DP() << "\n";
    }
    return 0;
}

扣的

#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
int n;
array<double, 24> X, Y;
array<int, (1 << 24)> dp;
array<array<int, 24>, 24> line;
int highbit(int x){
    int p = 0;
    for(int i = 4; i >= 0; i--){
        if(((1 << (p + (1 << i)))) <= x) p += (1 << i);
    }
    return p;
}
int DP(int n){
    int k;
    for(int i = 1; i < 1 << n; i++){
        dp[i] = n;
        k = highbit(i);
        for(int j = 0; j < n; j++){
            dp[i] = min(dp[i], dp[i & ~line[k][j]] + 1);
        }
    }
    return dp[(1 << n) - 1];
}
signed main(){
    int t;
    double a, b;
    cin >> t;
    while(t--){
        cin >> n;
        for(int i = 0; i < n; i++){
            cin >> X[i] >> Y[i];
        }
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                line[i][j] = (1 << i);
                if(X[i] == X[j]) continue;
                /*
                {yi = axi^2 + bxi ----- 1
                {yj = axj^2 + bxj ----- 2
                a :
                1 - 2 :
                yj(xi / xj) = axjxi + bxi
                yi - yj(xi / xj) = axi^2 - axjxi
                yi - yj(xi / xj) = a(xi^2 - xixj)
                a = (yi - yj(xi / xj)) / (xi^2 - xixj)
                a = (yixj - yjxi) / xixj(xi-xj)

                b :
                yi / xi = axi + b
                b = yi / xi - axi
                */
                a = (Y[i] * X[j] - Y[j] * X[i]) / (X[i] * X[j] * (X[i] - X[j]));
                b = (Y[i] / X[i]) - (a * X[i]);
                if(a >= 0) continue;
                for(int k = 0; k < n; k++){
                    if(Y[k] - (a * X[k] * X[k] + b * X[k]) < eps && Y[k] - (a * X[k] * X[k] + b * X[k]) > -eps){
                        line[i][j] |= (1 << k);
                    }
                }
            }
        }
        cout << DP(n) << "\n";
    }
    return 0;
}

大背包

W那麼大,怎麼辦

把背包反過來做啊

dp(i, j) = i \ value, first \ j \ items, min \ weight

狀態

dp(i, j) = min(dp(i, j - 1), dp(i - v_j, j - 1) + w_j)

轉移

複雜度

狀態 : 

轉移 : 

O(N\sum V)
O(1)
O(N\sum V)

扣的

#include <bits/stdc++.h>
using namespace std;
array<int, 104> W, V;
array<int, 100004> dp;
int bag(int n, int c){
    int ans = 0;
    for(int j = 0; j < n; j++){
        for(int i = 100000; i >= V[j]; i--){
            dp[i] = min(dp[i], dp[i - V[j]] + W[j]);
            if(dp[i] <= c) ans = max(ans, i);
        }
    }
    return ans;
}
signed main(){
    int n, w;
    cin >> n >> w;
    for(int &d : dp) d = w + 1;
    dp[0] = 0;
    for(int i = 0; i < n; i++){
        cin >> W[i] >> V[i];
    }
    cout << bag(n, w);
    return 0;
}

T跟A都那麼大,怎麼辦

跟上一題一樣?

這題是問最大耶

切兩半位元枚舉

最後再二分搜

N \le 40 !!!!

複雜度

狀態數 : 

算答案 : 

排序 : 

二分搜 : 

O(2^N)
O(N)
O(N2^N)
O(N2^N)
O(N2^N)

扣的

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
array<int, 44> A;
vector<int> bag(int l, int r){
    vector<int> V;
    for(int i = 0; i < (1 << (r - l + 1)); i++){
        V.pb(0);
        for(int j = 0; j <= r - l; j++){
            if(i & (1 << j)){
                V[i] += A[l + j];
            }
        }
    }
    return V;
}
int BIS(vector<int> L, vector<int> R, int c){
    int ans = 0;
    for(int l : L){
        if(c - l < R[0]) continue;
        ans = max(ans, l + *--upper_bound(R.begin(), R.end(), c - l));
    }
    return ans;
}
signed main(){
    int n, t;
    vector<int> L, R;
    cin >> n >> t;
    for(int i = 0; i < n; i++){
        cin >> A[i];
    }
    L = bag(0, n / 2 - 1);
    R = bag(n / 2, n - 1);
    sort(L.begin(), L.end());
    sort(R.begin(), R.end());
    cout << BIS(L, R, t);
    return 0;
}

Copy of 低批

By yennnn

Copy of 低批

  • 271