低批

扣要戳中間才會出來

複習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

01背包轉移式

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/2})
O(N)
O(N2^{N/2})
O(N2^{N/2})
O(N2^{\frac{N}{2}})

扣的

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

求救低批

Sum Of Subsets

\sum\limits_{k \subseteq S}^{S = 11010010}{A_k}

狀態

dp_i = \sum\limits_{k \subseteq i}{A_k}

暴力法

位元枚舉每個i的所有子集

複雜度

狀態 : 

算答案 : 

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

扣的

array<int, 1 << 10> A, dp;
void SOS(int n){
    for(int i = 0; i < (1 << n); i++){
        for(int k = 0; k <= i; k++){
            if(k & i == k) dp[i] += A[k];
        }
    }
}

強暴力法

枚舉每個i的所有子集,但是用&找

直接看扣

array<int, 1 << 16> A, dp;
void SOS(int n){
    for(int i = 0; i < (1 << n); i++){
        dp[i] = A[0];
        for(int k = i; k > 0; k = (k - 1) & i){
            dp[i] += A[k];
        }
    }
}

複雜度

狀態 : 

算答案 : 

O(2^N)
O(cnt \ of \ subsets)

複雜度

k \ bits\ do \ 2^k \ operations\\ C_k^N \ with \ k \ bits\\ \sum\limits_{k = 0}^{N}{C_k^N2^k}\\ = C_0^N2^0 + C_1^N2^1 + \dots + C_N^N2^N\\ = C_0^N2^01^N + C_1^N2^11^{N - 1} + \dots + C_N^N2^N1^0\\ = (2 + 1)^N = 3^N

低批作法

狀態

dp_i = SOS \ of \ i

轉移

array<int, 1 << 24> A, dp;
void SOS(int n){
    for(int i = 0; i < (1 << n); i++){
        dp[i] = A[i];
    }
    for(int k = 1; k < (1 << n); k <<= 1){
        for(int i = 0; i < (1 << n); i++){
            if(i & k) dp[i] += dp[i ^ k];
        }
    }
}

複雜度

狀態 : 

轉移 : 

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

題目

扣的

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 1 << 22> dp;
void SOS(int n){
    for(int k = 1; k < (1 << n); k <<= 1){
        for(int i = 1; i < (1 << n); i++){
            if(i & k) dp[i] += dp[i ^ k];
        }
    }
}
signed main(){
    int n, m, s, v, ans = 0;
    char t;
    cin >> n >> m;
    while(m--){
        s = 0;
        for(int i = 0; i < n; i++){
            s <<= 1;
            cin >> t;
            s |= (t ^ '0');
        }
        cin >> v;
        dp[s] = v;
    }
    SOS(n);
    sort(dp.begin() + 1, dp.begin() + (1 << n));
    for(int i = 1; i < (1 << n); i++){
        ans += i * dp[i];
    }
    cout << ans;
    return 0;
}

扣的

#include <bits/stdc++.h>
using namespace std;
int n;
array<int, 3> C;
array<int, 1 << 24> dp;
void SOS(){
    for(int k = 1; k < (1 << 24); k <<= 1){
        for(int i = 0; i < (1 << 24); i++){
            if(i & k) dp[i] += dp[i ^ k];
        }
    }
}
signed main(){
    int s, ans = 0, cnt;
    string S;
    cin >> n;
    while(n--){
        cin >> S;
        for(int i = 1; i < 8; i++){
            cnt = 0;
            s = 0;
            for(int j = 0; j < 3; j++){
                if(i & (1 << j)){
                    cnt++;
                    s |= 1 << (S[j] - 'a');
                }
            }
            if(cnt & 1) dp[s]++;
            else dp[s]--;
        }
    }
    SOS();
    for(int i = 0; i < (1 << 24); i++){
        ans ^= dp[i] * dp[i];
    }
    cout << ans;
    return 0;
}

滾動低批

當你覺得記憶體不夠時

當你轉移都從一個記憶體不大地方過來

\(dp_{i, j}\) 只從 \(dp_{i - 1, k}\) 轉移

那我只要紀錄 \(i\) 跟 \(i - 1\) 就好了

一個我覺得還不錯的實作方法

for(int i = 1, p = 1; i <= n; i++, p ^= 1){
    for(int j = 1; j <= m; j++){
        dp[p][j] = dp[!p][?]...;
    }
}

題目

#include <bits/stdc++.h>
using namespace std;
array<char, 2000004> S;
array<array<int, 15>, 2> dp;
int DP(int n){
    int ans = 0;
    for(int i = 1, p = 1; i <= n; i++, p ^= 1){
        for(int j = 0; j < 15; j++) dp[p][j] = dp[!p][j];
        if(S[i] == 'C'){
            dp[p][0]++; //C
            dp[p][5] = max(dp[p][5], dp[p][1]) + 1; //PC
            dp[p][7] = max(dp[p][7], dp[p][2]) + 1; //EC
            dp[p][12] = max(dp[p][12], dp[p][6]) + 1; //PEC
            dp[p][14] = max(dp[p][14], dp[p][8]) + 1; //EPC
        }else if(S[i] == 'P'){
            dp[p][1]++; //P
            dp[p][3] = max(dp[p][3], dp[p][0]) + 1; //8wCP
            dp[p][8] = max(dp[p][8], dp[p][2]) + 1; //EP
            dp[p][10] = max(dp[p][10], dp[p][4]) + 1; //CEP
            dp[p][13] = max(dp[p][13], dp[p][7]) + 1; //ECP
        }else{
            dp[p][2]++; //E
            dp[p][4] = max(dp[p][4], dp[p][0]) + 1; //CE
            dp[p][6] = max(dp[p][6], dp[p][1]) + 1; //PE
            dp[p][9] = max(dp[p][9], dp[p][3]) + 1; //CPE
            dp[p][11] = max(dp[p][11], dp[p][5]) + 1; //PCE
        }
    }
    for(int i = 0; i < 15; i++) ans = max(ans, dp[n & 1][i]);
    return ans;
}
signed main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> S[i];
    cout << DP(n) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define pb push_back
#define pii pair<int, int>
#define ff first
#define ss second
using namespace std;
const int inf = 1 << 30;
array<array<array<int, 2104>, 2104>, 2> dp;
vector<pii> B;
bool cmp(pii a, pii b){
    return a.ff > b.ff;
}
int DP(int n){
    int p = 0, sum = 0, k, ans = inf;
    for(array<array<int, 2104>, 2104> &d : dp){
        for(array<int, 2104> &dd : d){
            for(int &ddd : dd) ddd = inf;
        }
    }
    dp[0][0][0] = 0;
    for(auto [h, w] : B){
        p ^= 1;
        sum += w;
        for(int i = 0; i <= sum; i++){
            for(int j = 0; j <= sum; j++){
                k = sum - i - j;
                dp[p][i][j] = inf;
                if(i >= w) dp[p][i][j] = min(dp[p][i][j], dp[!p][i - w][j] + (i == w? h : 0));
                if(j >= w) dp[p][i][j] = min(dp[p][i][j], dp[!p][i][j - w] + (j == w? h : 0));
                if(k >= w) dp[p][i][j] = min(dp[p][i][j], dp[!p][i][j] + (k == w? h : 0));
            }
        }
    }
    for(int i = 1; i <= sum; i++){
        for(int j = 1; j <= sum; j++){
            k = sum - i - j;
            if(dp[p][i][j] == inf || !k) continue;
            ans = min(ans, dp[p][i][j] * max({i, j, k}));
        }
    }
    return ans;
}
signed main(){
    int t, n, h, w;
    cin >> t;
    while(t--){
        B.clear();
        cin >> n;
        for(int i = 1; i <= n; i++){
            cin >> h >> w;
            B.pb({h, w});
        }
        sort(B.begin(), B.end(), cmp);
        cout << DP(n) << "\n";
    }
    return 0;
}

#include <bits/stdc++.h>
using namespace std;
array<array<int, 5004>, 2> dp;
signed main(){
    int l, w, g, ans;
    while(cin >> l >> w){
        if(!l || !w) break;
        ans = 0;
        for(array<int, 5004> &d : dp){
            for(int &dd : d) dd = 0;
        }
        for(int i = 1, p = 1; i <= l; i++, p ^= 1){
            for(int j = 1; j <= w; j++){
                cin >> g;
                if(g == 2) dp[p][j] = 0;
                else dp[p][j] = min({dp[!p][j], dp[p][j - 1], dp[!p][j - 1]}) + 1;
                ans = max(ans, dp[p][j]);
            }
        }
        cout << ans * ans << "\n";
    }
    return 0;
}

插頭低批

當 \(N \times M\) 很小 : 16

可以位元DP耶!

0 代表沒東西,1 代表有骨牌然後爆搜放骨牌的位置

一些性質

放一個骨牌只會影響到附近幾格

已經操作過且影響不到的地方一定是放滿的

狀態

\(dp_{i, j, s} = \) 第 \(i, j\) 格,目前狀態為 \(s\) 的方法數

轉移

橫放方法數 + 直放方法數 + 不放方法數

複雜度

狀態 \(O(NM2^{min(N, M)})\) * 轉移\(O(1)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
array<array<array<int, 1 << 11>, 12>, 1004> dp;
int DP(int n, int m){
    dp[0][n][(1 << n + 1) - 1] = 1;
    for(int i = 1; i <= m; i++){
        for(int s = 0; s < (1 << n + 1); s++){
            dp[i][0][s] = dp[i - 1][n][s];
        }
        for(int j = 1; j <= n; j++){
            for(int s = 0; s < (1 << n + 1); s++){
                if(s & 1){
                    if(s & 2 && j > 1) dp[i][j][s] += dp[i][j - 1][((s ^ 3) >> 1) ^ (1 << n)];
                    if(s & (1 << n)) dp[i][j][s] += dp[i][j - 1][((s ^ (1 << n)) >> 1) ^ (1 << n)];
                }else dp[i][j][s] += dp[i][j - 1][(s >> 1) ^ (1 << n)];
                dp[i][j][s] %= mod;
            }
        }
    }
    return dp[m][n][(1 << n + 1) - 1];
}
signed main(){
    int n, m;
    cin >> n >> m;
    cout << DP(n, m) << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<array<int, 1 << 18>, 2> dp;
int DP(int n, int m, int k){
    int p = 1, opn, ans = 0;
    dp[0][(1 << (n + 2)) - 1] = 1;
    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++, p ^= 1){
            for(int s = 0; s < 1 << (n + 2); s++){
                dp[p][s] = 0;
                opn = (s & 1) + !!(s & 2) + !!(s & (1 << n)) + !!(s & (1 << (n + 1)));
                if(j > 1 && opn < 2) continue;
                dp[p][s] += dp[!p][s >> 1];
                dp[p][s] += dp[!p][(s >> 1) | (1 << (n + 1))];
                dp[p][s] %= k;
            }
        }
    }
    p ^= 1;
    for(int s = 1; s < 1 << (n + 2); s++){
        ans = (ans + dp[p][s]) % k;
    }
    return ans;
}
signed main(){
    int n, m, k;
    cin >> n >> m >> k;
    cout << DP(n, m, k) << "\n";
    return 0;
}

數位低批

狀態

\(dp_{d, p, t, z} = \) 第 \(d\) 位數,第 \(d + 1\) 位數為 \(p\),前綴是否為最高限制,前綴是否為 \(0\) 的彩虹數數量

轉移

\(if(t = 1) : \) 繼續最高限制 + 非最高限制 \((num_d \ != p)\)

\(else \ if(z = 1) : \) 繼續0 + 非0

\(else : \) \(num_d \ != p\)

複雜度

狀態 \(O(Alog_AC)\) * 轉移 \(O(A)\)

假設 \(A\) 進位

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
array<array<array<array<int, 2>, 2>, 10>, 20> dp;
vector<int> dig;
int DP(int d, int p, bool t, bool z){
    if(d < 0) return 1;
    if(dp[d][p][t][z]) return dp[d][p][t][z];
    int sum = 0;
    if(t){
        if(z || p) sum += DP(d - 1, 0, !dig[d], z);
        for(int i = 1; i <= dig[d]; i++){
            if(i != p) sum += DP(d - 1, i, i == dig[d], 0);
        }
    }else if(z){
        for(int i = 0; i <= 9; i++){
            sum += DP(d - 1, i, 0, !i);
        }
    }else{
        for(int i = 0; i <= 9; i++){
            if(i != p) sum += DP(d - 1, i, 0, 0);
        }
    }
    return dp[d][p][t][z] = sum;
}
void DEC(int x){
    dig.clear();
    for(int i = 0; i < 20; i++){
        for(int j = 0; j < 10; j++){
            for(int k : {0, 1}){
                for(int l : {0, 1}) dp[i][j][k][l] = 0;
            }
        }
    }
    while(x){
        dig.pb(x % 10);
        x /= 10;
    }
}
signed main(){
    int a, b;
    cin >> a >> b;
    a--;
    DEC(a);
    if(a >= 0) a = DP(dig.size() - 1, 0, 1, 1);
    DEC(b);
    b = DP(dig.size() - 1, 0, 1, 1);
    cout << b - max(a, 0ll) << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
#define pb push_back
#define int long long
#define pii pair<int, int>
#define ff first
#define ss second
using namespace std;
const int mod = 998244353;
int elon = 0;
array<int, 10004> ten;
array<array<bitset<1 << 10>, 2>, 10004> vis;
array<array<array<pii, 1 << 10>, 2>, 10004> dp;
vector<int> D;
pii DP(int d, bool t, int s){
    if(d < 0) return {0, (s & elon) == elon};
    if(vis[d][t][s]) return dp[d][t][s];
    vis[d][t][s] = 1;
    int sum = 0, cnt = 0;
    pii tmp;
    if(t){
        tmp = DP(d - 1, D[d] == 0, s | !!s);
        sum += tmp.ff, cnt += tmp.ss;
        for(int i = 1; i <= D[d]; i++){
            auto [k, c] = DP(d - 1, i == D[d], s | (1 << i));
            sum = (sum + k + i * ten[d] * c) % mod;
            cnt = (cnt + c) % mod;
        }
    }else if(!s){
        tmp = DP(d - 1, 0, 0);
        sum += tmp.ff, cnt += tmp.ss;
        for(int i = 1; i < 10; i++){
            auto [k, c] = DP(d - 1, 0, s | (1 << i));
            sum = (sum + k + i * ten[d] * c) % mod;
            cnt = (cnt + c) % mod;
        }
    }else{
        for(int i = 0; i < 10; i++){
            auto [k, c] = DP(d - 1, 0, s | (1 << i));
            sum = (sum + k + i * ten[d] * c) % mod;
            cnt = (cnt + c) % mod;
        }
    }
    return dp[d][t][s] = {sum, cnt};
}
signed main(){
    int m, c, p = 1;
    string N;
    cin >> N >> m;
    ten[0] = 1;
    for(char n : N){
        D.pb(n ^ '0');
        ten[p] = 10 * ten[p - 1] % mod;
        p++;
    }
    reverse(D.begin(), D.end());
    while(m--){
        cin >> c;
        elon |= (1 << c);
    }
    cout << DP(D.size() - 1, 1, 0).ff << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int m = 1e9 + 7;
int d;
array<array<array<bool, 2>, 104>, 10004> vis;
array<array<array<int, 2>, 104>, 10004> dp;
vector<int> D;
int mod(int x, int p){
    return ((x % p) + p) % p;
}
int DP(int i, int j, bool t){
    if(i < 0) return j == 0;
    if(vis[i][j][t]) return dp[i][j][t];
    vis[i][j][t] = 1;
    int sum = 0;
    if(t){
        for(int k = 0; k <= D[i]; k++){
            sum += DP(i - 1, mod(j - k, d), k == D[i]);
        }
    }else{
        for(int k = 0; k < 10; k++){
            sum += DP(i - 1, mod(j - k, d), 0);
        }
    }
    return dp[i][j][t] = mod(sum, m);
}
signed main(){
    string K;
    cin >> K >> d;
    for(char k : K) D.pb(k ^ '0');
    reverse(D.begin(), D.end());
    cout << mod(DP(D.size() - 1, 0, 1) - 1, m) << "\n";
    return 0;
}

資結優化

BIT

長得很像LIS的東西

IS

狀態

\(dp_i = \) 以第 \(i\) 個當最後一個的 IS 數量

轉移

\(dp_i = \sum_{j = 1, X_j < X_i}^{i}{dp_j} + 1\)

優化

用BIT維護前綴和

複雜度

Before : 狀態 \(O(N)\) * 轉移 \(O(N)\)

After : 狀態 \(O(N)\) * 轉移 \(O(logN)\)

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
int mod = 1e9 + 7;
array<int, 200004> X, BIT;
vector<pair<int, int>> S;
void update(int p, int x){
    for(; p < 200004; p += p & -p) BIT[p] = (BIT[p] + x) % mod;
}
int query(int p){
    int sum = 0;
    for(; p; p -= p & -p) sum = (sum + BIT[p]) % mod;
    return sum;
}
void pear(vector<pair<int, int>> &V){
    int lst = 0, k = 0;
    sort(V.begin(), V.end());
    for(auto [x, p] : V){
        k += x != lst;
        X[p] = k;
        lst = x;
    }
}
int DP(int n){
    int sum = 0, t;
    for(int i = 1; i <= n; i++){
        t = query(X[i] - 1) + 1;
        sum = (sum + t) % mod;
        update(X[i], t);
    }
    return sum;
}
signed main(){
    int n, x;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> x;
        S.pb({x, i});
    }
    pear(S);
    cout << DP(n) << "\n";
    return 0;
}

題目

 扣

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
array<int, 200004> P, Q, R, BIT;
vector<int> D;
bool cmp(int a, int b){
    return R[a] > R[b];
}
void update(int p, int x){
    for(; p < 200004; p += p & -p) BIT[p] = max(BIT[p], x);
}
int query(int p){
    int mix = 0;
    for(; p; p -= p & -p) mix = max(mix, BIT[p]);
    return mix;
}
void DIV(int x){
    D.clear();
    for(int i = 1; i * i <= x; i++){
        if(x % i == 0){
            D.pb(i);
            if(i * i != x) D.pb(x / i);
        }
    }
    sort(D.begin(), D.end(), cmp);
}
int DP(int n){
    for(int i = 1; i <= n; i++){
        DIV(Q[i]);
        for(int d : D){
            update(R[d], query(R[d] - 1) + 1);
        }
    }
    return query(n);
}
signed main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> P[i];
        R[P[i]] = i;
    }
    for(int i = 1; i <= n; i++) cin >> Q[i];
    cout << DP(n) << "\n";
    return 0;
}

#include <bits/stdc++.h>
using namespace std;
array<int, 800004> BIT;
void update(int p, int x){
    for(; p < 800004; p += p & -p) BIT[p] += x;
}
int query(int p){
    int sum = 0;
    for(; p; p -= p & -p) sum += BIT[p];
    return sum;
}
int find(int x){
    int p = 0, sum = 0;
    for(int i = 1 << 19; i; i >>= 1){
        if(p + i < 800003 && sum + BIT[p + i] < x){
            p += i;
            sum += BIT[p];
        }
    }
    return p + 1;
}
signed main(){
    int n, s, t;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> s >> t;
        s++, t++;
        update(s, 1);
        update(find(query(t) + 1), -1);
    }
    cout << query(n) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 200004> H, A, dp, BIT;
void update(int p, int x){
    for(; p < 200004; p += p & -p) BIT[p] = max(BIT[p], x);
}
int query(int p){
    int ans = 0;
    for(; p; p -= p & -p) ans = max(ans, BIT[p]);
    return ans;
}
int DP(int n){
    int ans = 0;
    for(int i = 1; i <= n; i++){
        dp[i] = query(H[i]) + A[i];
        update(H[i], dp[i]);
        ans = max(ans, dp[i]);
    }
    return ans;
}
signed main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> H[i];
    for(int i = 1; i <= n; i++) cin >> A[i];
    cout << DP(n) << "\n";
    return 0;
}

Trie

狀態

\(dp_i = i \ to \ N\) 的方法數

轉移

\(dp_i = \sum_{j = i}^{N}{dp_j \times cnt_{S[i, j]}}\)

優化

砸個 Trie 就可以邊跑邊算了

複雜度

狀態 \(O(N)\) * 轉移 \(O(N)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int k = 0;
array<int, 5004> dp;
array<int, 1000004> cnt;
array<array<int, 26>, 1000004> trie;
void update(int p, int i, string &S){
    if(i == S.size()){
        cnt[p]++;
        return;
    }
    if(!trie[p][S[i] - 'a']) trie[p][S[i] - 'a'] = ++k;
    update(trie[p][S[i] - 'a'], i + 1, S);
}
int query(int p, int i, string &S){
    if(i == S.size() + 1) return 0;
    int sum = cnt[p] * dp[i];
    if(trie[p][S[i] - 'a']) sum += query(trie[p][S[i] - 'a'], i + 1, S);
    return sum % mod;
}
signed main(){
    int n, m;
    string S, T;
    cin >> S >> m;
    n = S.size();
    for(int i = 0; i < m; i++){
        cin >> T;
        update(0, 0, T);
    }
    dp[n] = 1;
    for(int i = n - 1; i >= 0; i--){
        dp[i] = query(0, i, S);
    }
    cout << dp[0] << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
using namespace std;
const int inf = 1 << 30;
int k = 1;
array<int, 2004> cnt, F;
array<array<int, 3>, 2004> trie;
array<array<int, 2004>, 10004> dp;
void update(int p, int i, string &S, int w){
    if(i == S.size()){
        cnt[p] += w;
        return;
    }
    if(!trie[p][S[i]]) trie[p][S[i]] = ++k;
    update(trie[p][S[i]], i + 1, S, w);
}
int fail(int p, char c){
    if(trie[p][c]) return trie[p][c];
    return fail(F[p], c);
}
void AC(int s){
    int u;
    queue<int> Q;
    Q.push(s), F[s] = 0;
    while(!Q.empty()){
        u = Q.front();
        Q.pop();
        for(char i = 0; i < 3; i++){
            if(!trie[u][i]) continue;
            F[trie[u][i]] = fail(F[u], i);
            cnt[trie[u][i]] += cnt[F[trie[u][i]]];
            Q.push(trie[u][i]);
        }
    }
}
int DP(string &S){
    int n = S.size(), p, ans = -inf;
    dp[0][1] = 0;
    for(int i = 2; i <= k; i++) dp[0][i] = -inf;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= k; j++) dp[i][j] = -inf;
        for(int j = 1; j <= k; j++){
            for(char c = 0; c < 3; c++){
                if(S[i - 1] != c && S[i - 1] < 3) continue;
                p = fail(j, c);
                dp[i][p] = max(dp[i][p], dp[i - 1][j] + cnt[p]);
            }
        }
    }
    for(int i = 1; i <= k; i++){
        ans = max(ans, dp[n][i]);
    }
    return ans;
}
signed main(){
    int n, m, w;
    string S, T;
    for(int i = 0; i < 3; i++) trie[0][i] = 1;
    cin >> n >> m;
    for(int i = 0; i < m; i++){
        cin >> T >> w;
        for(char &t : T){
            if(t == 'r') t = 0;
            else if(t == 'g') t = 1;
            else t = 2;
        }
        update(1, 0, T, w);
    }
    AC(1);
    cin >> S;
    for(char &s : S){
        if(s == 'r') s = 0;
        else if(s == 'g') s = 1;
        else if(s == 'b') s = 2;
        else s = 3;
    }
    cout << DP(S) << "\n";
    return 0;
}

矩陣優化

費氏數列

狀態

\(dp_i = \) 費氏數列第 \(i\) 項

轉移

\(dp_i = dp_{i - 1} + dp_{i - 2}\)

優化

搞一個狀態矩陣 + 一個轉移矩陣

\(\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \times \begin{bmatrix} dp_{i - 1} \\ dp_{i - 2} \end{bmatrix} = \begin{bmatrix} dp_i \\ dp_{i - 1} \end{bmatrix}\)

\(\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^k \times \begin{bmatrix} dp_{i} \\ dp_{i - 1} \end{bmatrix} = \begin{bmatrix} dp_{i + k} \\ dp_{i + k - 1} \end{bmatrix}\)

那我們只要能快速的算矩陣冪就可以加速DP了

快速冪

\(x^k = (x^{\frac{k}{2}})^2\)

複雜度

Before : \(O(K)\)

After : \(O(N^3logK)\)

\(N = \) 轉移矩陣邊長

貼心提醒

要初始化

不要忘記

不要錯過

因為非常好WA

BY 一個已經被初始化搞了10次以上仍然不會記得要初始化的人

題目

#include <bits/stdc++.h>
#define int long long
#define matrix array<array<int, 2>, 2>
using namespace std;
const int mod = 1e9 + 7;
matrix T, dp;
matrix mul(matrix A, matrix B){
    matrix C;
    for(int i : {0, 1}){
        for(int j : {0, 1}){
            C[i][j] = 0;
            for(int k : {0, 1}){
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
            }
        }
    }
    return C;
}
matrix exp(matrix X, int k){
    matrix P;
    for(int i : {0, 1}){
        for(int j : {0, 1}) P[i][j] = 0;
        P[i][i] = 1;
    }
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) P = mul(P, X);
        X = mul(X, X);
    }
    return P;
}
signed main(){
    int x1, x2, a, b, n;
    cin >> x1 >> x2 >> a >> b >> n;
    T[0][0] = b, T[0][1] = a, T[1][0] = 1;
    dp[0][0] = x2, dp[1][0] = x1;
    cout << mul(exp(T, n - 2), dp)[0][0] << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
#define matrix array<array<int, 204>, 204>
using namespace std;
const int no = -(1ll << 60);
int n;
array<int, 204> H;
matrix G;
matrix mul(matrix A, matrix B){
    matrix C;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            C[i][j] = no;
            for(int k = 1; k <= n; k++){
                C[i][j] = max(C[i][j], A[i][k] + B[k][j]);
            }
        }
    }
    return C;
}
matrix exp(matrix X, int k){
    matrix P;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            P[i][j] = no;
        }
        P[i][i] = 0;
    }
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) P = mul(P, X);
        X = mul(X, X);
    }
    return P;
}
signed main(){
    int m, s, t, a, b, ans = no;
    cin >> n >> m >> s >> t;
    for(int i = 1; i <= n; i++) cin >> H[i];
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++) G[i][j] = no;
    }
    while(m--){
        cin >> a >> b;
        G[a][b] = H[b];
    }
    G = exp(G, t);
    for(int i = 1; i <= n; i++){
        ans = max(ans, G[s][i]);
    }
    cout << ans << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
#define matrix array<array<int, 3>, 3>
using namespace std;
const int mod = 998244353;
matrix T, dp;
matrix mul(matrix A, matrix B){
    matrix C;
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            C[i][j] = 0;
            for(int k = 0; k < 3; k++){
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
            }
        }
    }
    return C;
}
matrix exp(matrix X, int k){
    matrix P;
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++) P[i][j] = 0;
        P[i][i] = 1;
    }
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) P = mul(P, X);
        X = mul(X, X);
    }
    return P;
}
signed main(){
    int t, n;
    dp[0][0] = dp[2][0] = 1;
    T[0][0] = T[0][1] = T[0][2] = T[1][0] = T[2][2] = 1;
    cin >> t;
    while(t--){
        cin >> n;
        cout << mul(exp(T, n - 1), dp)[0][0] << "\n";
    }
    return 0;
}

#include <bits/stdc++.h>
#define int long long
#define matrix array<array<int, 54>, 54>
using namespace std;
const int mod = 1e9 + 7;
int n;
matrix mul(matrix A, matrix B){
    matrix C;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            C[i][j] = 0;
            for(int k = 1; k <= n; k++){
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
            }
        }
    }
    return C;
}
matrix exp(matrix X, int k){
    matrix P;
    for(int i = 1; i <= n; i++){
       for(int j = 1; j <= n; j++) P[i][j] = 0;
       P[i][i] = 1;
    }
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) P = mul(P, X);
        X = mul(X, X);
    }
    return P;
}
signed main(){
    int k, sum = 0;
    matrix G;
    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            cin >> G[i][j];
        }
    }
    G = exp(G, k);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            sum = (sum + G[i][j]) % mod;
        }
    }
    cout << sum << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
#define matrix array<array<int, 101>, 101>
using namespace std;
const int mod = 1e9 + 7;
matrix G;
matrix mul(matrix A, matrix B, int n){
    matrix C;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            C[i][j] = 0;
            for(int k = 1; k <= n; k++){
                C[i][j] += (A[i][k] * B[k][j]) % mod;
            }
            C[i][j] %= mod;
        }
    }
    return C;
}
int walk(int n, int k){
    matrix W;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            W[i][j] = 0;
        }
        W[i][i] = 1;
    }
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) W = mul(G, W, n);
        G = mul(G, G, n);
    }
    return W[n][1];
}
signed main(){
    int n, m, k, a, b;
    cin >> n >> m >> k;
    while(m--){
        cin >> a >> b;
        G[b][a]++;
    }
    cout << walk(n, k);
    return 0;
}

#include <bits/stdc++.h>
#define int long long
#define matrix array<array<int, 101>, 101>
using namespace std;
matrix G, W;
int min(int a, int b){
    if(a < 0) return b;
    if(b < 0) return a;
    return a < b? a : b;
}
matrix mul(matrix A, matrix B, int n){
    matrix C;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            C[i][j] = -1;
            for(int k = 1; k <= n; k++){
                if(A[i][k] < 0 || B[k][j] < 0) continue;
                C[i][j] = min(C[i][j], A[i][k] + B[k][j]);
            }
        }
    }
    return C;
}
int walk(int n, int k){
    k--;
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) W = mul(G, W, n);
        G = mul(G, G, n);
    }
    return W[n][1];
}
signed main(){
    int n, m, k, a, b, c;
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            G[i][j] = -1;
            W[i][j] = -1;
        }
    }
    while(m--){
        cin >> a >> b >> c;
        G[b][a] = min(G[b][a], c);
        W[b][a] = min(W[b][a], c);
    }
    cout << walk(n, k);
    return 0;
}

#include <bits/stdc++.h>
#define int unsigned
#define matrix array<array<int, 2>, 2>
using namespace std;
matrix T, dp;
matrix mul(matrix A, matrix B){
    matrix C;
    for(int i : {0, 1}){
        for(int j : {0, 1}){
            C[i][j] = 0;
            for(int k : {0, 1}){
                C[i][j] += A[i][k] * B[k][j];
            }
        }
    }
    return C;
}
matrix exp(matrix X, int k){
    matrix P;
    for(int i : {0, 1}){
        for(int j : {0, 1}) P[i][j] = 0;
        P[i][i] = 1;
    }
    for(int i = 1; i <= k; i <<= 1){
        if(i & k) P = mul(P, X);
        X = mul(X, X);
    }
    return P;
}
signed main(){
    int a, b, x, y;
    signed n;
    while(cin >> n){
        if(n < 0) break;
        cin >> a >> b >> x >> y;
        T[0][0] = y, T[0][1] = x;
        T[1][0] = 1, T[1][1] = 0;
        dp[0][0] = b, dp[1][0] = a;
        cout << mul(exp(T, n), dp)[1][0] << "\n";
    }
    return 0;
}

單調隊列優化

單調隊列

作法

找到右邊第一個大於等於自己的

暴力作法

往右邊掃直到遇到比自己大的

(不知道為什麼聽起來很像廢話)

一些性質

右邊比自己小的都不重要

比右邊東西小的也對自己不重要

作法

搞一個 stack,從右到左丟每一隻牛的高度和位置進去。當top高度小於自己時,pop。答案就是無法被pop掉的位置和自己位置的距離

複雜度

\(O(N)\)

#include <bits/stdc++.h>
using namespace std;
array<int, 1000004> H, see;
stack<pair<int, int>> S;
signed main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> H[i];
    S.push({2147483647, n});
    for(int i = n; i; i--){
        while(1){
            auto [h, p] = S.top();
            if(H[i] > h) S.pop();
            else{
                see[i] = p - i;
                break;
            }
        }
        S.push({H[i], i});
    }
    for(int i = 1; i <= n; i++) cout << see[i] << "\n";
    return 0;
}

拿單調隊列砸DP

狀態

\(dp_{i, j} = \) 第 \(i\) 種物品,花了 \(j\) 元,最大價值

轉移

\(dp_{i, j} = max_{k = j - W_i \times C_i}^j(dp_{i - 1, k} + \frac{j - k}{W_i} \times M_i)\)

優化

取max可以用單調隊列耶

優化

維護一個deque,把過期的東西從前面丟出去,新東西從後面進去,順便丟掉一些不會用到的狀態。

複雜度

Before : \(O(NTC)\)

After : \(O(NT)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
int l, r;
array<int, 100004> W, M, C;
array<int, 1000004> Q;
array<array<int, 1000004>, 2> dp;
int DP(int n, int t){
    int ans = 0;
    for(int i = 1, p = 1; i <= n; i++, p ^= 1){
        for(int j = 0; j < W[i]; j++){
            l = 1, r = 0;
            for(int k = j; k <= t; k += W[i]){
                if(r >= l && Q[l] + W[i] * C[i] < k) l++;
                while(r >= l && dp[!p][Q[r]] + (k - Q[r]) / W[i] * M[i] <= dp[!p][k]) r--;
                Q[++r] = k;
                dp[p][k] = dp[!p][Q[l]] + (k - Q[l]) / W[i] * M[i];
                ans = max(ans, dp[p][k]);
            }
        }
    }
    return ans;
}
signed main(){
    int n, t;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> W[i] >> M[i] >> C[i];
    }
    cin >> t;
    cout << DP(n, t) << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 100004> H, W, Q, dp;
multiset<int> S;
int DP(int n, int k){
    int l = 1, r = 0, len = 0;
    Q[++r] = 0;
    for(int i = 1; i <= n; i++){
        len += W[i];
        while(r > l && len > k){
            S.erase(dp[Q[l]] + H[Q[l + 1]]);
            len -= W[++Q[l]];
            if(Q[l] == Q[l + 1]) l++;
            else if(len <= k) S.insert(dp[Q[l]] + H[Q[l + 1]]);
        }
        while(r > l && H[i] >= H[Q[r]]){
            S.erase(dp[Q[r - 1]] + H[Q[r]]);
            r--;
        }
        S.insert(dp[Q[r]] + H[i]);
        Q[++r] = i;
        dp[i] = *S.begin();
    }
    return dp[n];
}
signed main(){
    int n, l;
    cin >> n >> l;
    for(int i = 1; i <= n; i++){
        cin >> H[i] >> W[i];
    }
    cout << DP(n, l) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 200004> K;
stack<pair<int, int>> F;
int run(int n){
    int ans = 0, p;
    for(int i = 1; i <= n; i++){
        p = i;
        while(!F.empty()){
            auto [l, h] = F.top();
            if(K[i] < h){
                ans = max(ans, h * (i - l));
                F.pop();
                p = l;
            }
            else break;
        }
        F.push({p, K[i]});
    }
    while(!F.empty()){
        auto [l, h] = F.top();
        F.pop();
        ans = max(ans, h * (n - l + 1));
    }
    return ans;
}
signed main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> K[i];
    cout << run(n) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define ff first
#define ss second
using namespace std;
array<int, 1004> H;
array<array<char, 1004>, 1004> G;
stack<pair<int, int>> S;
int run(int n, int m){
    int ans = 0, now;
    for(int j = 1; j <= n; j++){
        S.push({0, 0});
        for(int i = 1; i <= m; i++){
            now = i;
            if(G[j][i] == '*') H[i] = 0;
            else H[i]++;
            while(!S.empty() && H[i] <= S.top().ff){
                auto [h, p] = S.top();
                S.pop();
                ans = max(ans, (i - p) * h);
                now = p;
            }
            S.push({H[i], now});
        }
        while(!S.empty()){
            auto [h, p] = S.top();
            S.pop();
            ans = max(ans, (m - p + 1) * h);
        }
    }
    return ans;
}
signed main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cin >> G[i][j];
        }
    }
    cout << run(n, m) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 1004> H;
array<array<char, 1004>, 1004> G;
array<array<int, 1004>, 1004> cnt;
void run(int n, int m){
    int now;
    for(int i = 1; i <= n; i++){
        stack<pair<int, int>> S;
        S.push({0, 0});
        for(int j = 1; j <= m; j++){
            now = j;
            if(G[i][j] == '*') H[j] = 0;
            else H[j]++;
            while(!S.empty()){
                auto [h, p] = S.top();
                if(H[j] < h){
                    cnt[h][j - p]++;
                    S.pop();
                    now = p;
                    auto [h2, p2] = S.top();
                    cnt[max(h2, H[j])][j - p]--;
                }else{
                    break;
                }
            }
            S.push({H[j], now});
        }
    }
    for(int i = n; i; i--){
        for(int j = m; j; j--){
            cnt[i][j] += cnt[i][j + 1];
        }
    }
    for(int i = n; i; i--){
        for(int j = m; j; j--){
            cnt[i][j] += cnt[i + 1][j] + cnt[i][j + 1] - cnt[i + 1][j + 1];
        }
    }
}
signed main(){
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cin >> G[i][j];
        }
        G[i][m + 1] = '*';
    }
    run(n, m + 1);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cout << cnt[i][j] << " ";
        }
        cout << "\n";
    }
    return 0;
}

#include <bits/stdc++.h>
#define int long long
using namespace std;
int l, r;
array<int, 104> H, S, K;
array<int, 100004> Q;
array<array<int, 100004>, 2> dp;
int DP(int n, int x){
    int p = 0, ans = 0;
    for(int i = 1; i <= n; i++){
        p ^= 1;
        for(int j = 0; j < H[i]; j++){
            l = 0, r = -1;
            for(int k = j; k <= x; k += H[i]){
                if(l <= r && Q[l] + K[i] * H[i] < k) l++;
                while(l <= r && dp[p ^ 1][k] >= dp[p ^ 1][Q[r]] + (k - Q[r]) / H[i] * S[i]) r--;
                Q[++r] = k;
                dp[p][k] = dp[p ^ 1][Q[l]] + (k - Q[l]) / H[i] * S[i];
                ans = max(ans, dp[p][k]);
            }
        }
    }
    return ans;
}
signed main(){
    int n, x;
    cin >> n >> x;
    for(int i = 1; i <= n; i++) cin >> H[i];
    for(int i = 1; i <= n; i++) cin >> S[i];
    for(int i = 1; i <= n; i++) cin >> K[i];
    cout << DP(n, x) << "\n";
    return 0;
}

斜率優化

單調的斜率優化

狀態

\(dp_i = i\) 當最後一個的答案

轉移

\(dp_i = max_{j < i}(a(\sum\limits_{k = j + 1}^{i}{X_k})^2 + b(\sum\limits_{k = j + 1}^{i}{X_k}) + c + dp_j)\)

不是斜率優化的優化

\(dp_i = max_{j < i}(a(S_i - S_j)^2 + b(S_i - S_j) + c + dp_j)\)

\(S_i = \sum\limits_{j = 1}^{i}{X_j}\)

斜率優化

把轉移變成一條直線

\(y=  ax + b\)

斜率優化

\(dp_i = (-2aS_j)(S_i) + (aS_j^2 - bS_j + dp_j) + aS_i^2 + bS_i + c\)

範測

維護線段

斜率單調 & 查詢單調

=> 開單調隊列存

複雜度

Before : \(O(N^2)\)

After : \(O(N)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
struct line{
    double a, b;
    double operator*(double x){
        return a * x + b;
    }
    pair<double, double> operator^(line f){
        double x, y;
        x = (f.b - b) / (a - f.a);
        y = f * x;
        return {x, y};
    }
};
array<double, 1000004> X, dp;
array<line, 1000004> Q;
int DP(int n, double a, double b, double c){
    int l = 1, r = 0;
    line f;
    Q[++r] = {0, 0};
    for(int i = 1; i <= n; i++){
        while(r > l && Q[l] * X[i] <= Q[l + 1] * X[i]) l++;
        dp[i] = Q[l] * X[i] + a * X[i] * X[i] + b * X[i] + c;
        f = {2 * -a * X[i], a * X[i] * X[i] - b * X[i] + dp[i]};
        while(r > l){
            auto [x, y] = f ^ Q[r - 1];
            if(Q[r] * x <= y) r--;
            else break;
        }
        Q[++r] = f;
    }
    return (int)dp[n];
}
signed main(){
    int n;
    double a, b, c;
    cin >> n >> a >> b >> c;
    for(int i = 1; i <= n; i++){
        cin >> X[i];
        X[i] += X[i - 1];
    }
    cout << DP(n, a, b, c) << "\n";
    return 0;
}

不怎麼單調的斜率優化

斜率跟查詢都不單調耶

李超線段樹

每個節點存這個節點的區間中點最好的一條線

一棵裸的李超線段樹

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define lc (p << 1)
#define rc ((p << 1) | 1)
using namespace std;
struct line{
    int a, b;
    int operator*(int x){
        return a * x + b;
    }
};
array<line, C> seg;
void update(int p, int l, int r, line f){
    if(l == r){
        if(seg[p] * l < f * l) seg[p] = f;
        return;
    }
    if(f.a < seg[p].a) swap(seg[p], f);
    if(seg[p] * mid <= f * mid){
        swap(f, seg[p]);
        update(lc, l, mid, f);
    }else update(rc, mid + 1, r, f);
}
int query(int p, int l, int r, int x){
    if(l == r) return seg[p] * x;
    if(x <= mid) return max(query(lc, l, mid, x), seg[p] * x);
    else return max(query(rc, mid + 1, r, x), seg[p] * x);
}

複雜度

\(O(NlogC)\)

#include <bits/stdc++.h>
#define int long long
#define mid ((l + r) >> 1)
#define lc (p << 1)
#define rc ((p << 1) | 1)
using namespace std;
const int inf = 1 << 20;
struct line{
    int a, b;
    int operator*(int x){
        return a * x + b;
    }
};
array<int, 200004> F, S, dp;
array<line, (1 << 22) + 4> seg;
void build(){
    for(line &f : seg) f = {inf, inf * inf};
}
void update(int p, int l, int r, line f){
    if(l == r){
        if(seg[p] * l > f * l) seg[p] = f;
        return;
    }
    if(f.a > seg[p].a) swap(seg[p], f);
    if(seg[p] * mid >= f * mid){
        swap(f, seg[p]);
        update(lc, l, mid, f);
    }else update(rc, mid + 1, r, f);
}
int query(int p, int l, int r, int x){
    if(l == r) return seg[p] * x;
    if(x <= mid) return min(query(lc, l, mid, x), seg[p] * x);
    else return min(query(rc, mid + 1, r, x), seg[p] * x);
}
int DP(int n, int x){
    update(1, 1, inf, {x, 0});
    for(int i = 1; i <= n; i++){
        dp[i] = query(1, 1, inf, S[i]);
        update(1, 1, inf, {F[i], dp[i]});
    }
    return dp[n];
}
signed main(){
    int n, x;
    cin >> n >> x;
    build();
    for(int i = 1; i <= n; i++) cin >> S[i];
    for(int i = 1; i <= n; i++) cin >> F[i];
    cout << DP(n, x) << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
struct line{
    double a, b;
    double operator*(double x){
        return a * x + b;
    }
    pair<double, double> operator^(line f){
        double x, y;
        x = (b - f.b) / (f.a - a);
        y = f * x;
        return {x, y};
    }
};
array<double, 200004> F, S, dp;
array<line, 200004> Q;
int DP(int n, double c){
    int l = 1, r = 0;
    line f;
    Q[++r] = {c, 0};
    for(int i = 1; i <= n; i++){
        while(r > l && Q[l] * S[i] >= Q[l + 1] * S[i]) l++;
        dp[i] = Q[l] * S[i];
        f = {F[i], dp[i]};
        while(r > l){
            auto [x, y] = f ^ Q[r - 1];
            if(Q[r] * x >= y) r--;
            else break;
        }
        Q[++r] = f;
    }
    return (int)dp[n];
}
signed main(){
    int n;
    double x;
    cin >> n >> x;
    for(int i = 1; i <= n; i++) cin >> S[i];
    for(int i = 1; i <= n; i++) cin >> F[i];
    cout << DP(n, x) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define pb push_back
#define int long long
#define mid ((l + r) >> 1)
using namespace std;
struct ooo{
    int d, p, r, g;
};
struct line{
    int a, b;
    int operator*(int x){
        return a * x + b;
    }
};
const int inf = 1ll << 30;
int k = 0;
array<int, 100004> dp;
vector<int> lc, rc;
vector<line> seg;
vector<ooo> Q;
bool cmp(ooo a, ooo b){
    return a.d < b.d;
}
void add(){
    lc.pb(0), rc.pb(0), seg.pb({0, 0});
}
void update(int p, int l, int r, line f){
    if(l == r){
        if(seg[p] * l < f * l) seg[p] = f;
        return;
    }
    if(f.a < seg[p].a) swap(seg[p], f);
    if(seg[p] * mid <= f * mid){
        swap(f, seg[p]);
        if(!lc[p]) lc[p] = ++k, add();
        update(lc[p], l, mid, f);
    }else{
        if(!rc[p]) rc[p] = ++k, add();
        update(rc[p], mid + 1, r, f);
    }
}
int query(int p, int l, int r, int x){
    if(l == r) return seg[p] * x;
    if(x <= mid) return max(seg[p] * x, lc[p]? query(lc[p], l, mid, x) : 0);
    else return max(seg[p] * x, rc[p]? query(rc[p], mid + 1, r, x) : 0);
}
int DP(int n){
    int i = 1;
    add();
    for(auto [d, p, r, g] : Q){
        dp[i] = query(0, 0, inf, d);
        if(dp[i] >= p) update(0, 0, inf, {g, dp[i] - g * (d + 1) - p + r});
        i++;
    }
    return dp[n];
}
signed main(){
    int n, c, d, t, p, r, g;
    cin >> n >> c >> d;
    for(int i = 1; i <= n; i++){
        cin >> t >> p >> r >> g;
        Q.pb({t, p, r, g});
    }
    Q.pb({0, -c, 0, 0});
    Q.pb({d + 1, 0, 0, 0});
    sort(Q.begin(), Q.end(), cmp);
    cout << DP(n + 2) << "\n";
    return 0;
}

斜率優化 - Extreme

 線段樹套李超線段樹

#include <bits/stdc++.h>
#define int long long
#define mid ((l + r) >> 1)
#define lp (p << 1)
#define rp ((p << 1) | 1)
using namespace std;
const int inf = 1ll << 30;
struct line{
    int a, b;
    int operator*(int x){
        return a * x + b;
    }
    bool operator==(line f){
        if(a == f.a && b == f.b) return 1;
        return 0;
    }
};
struct superlee{
    line f;
    superlee *lc, *rc;
    superlee(){f = {-inf, -inf * inf}, lc = rc = nullptr;}
    void update(int l, int r, line g){
        if(f == g) return;
        if(l == r){
            if(g * mid > f * mid) f = g;
            return;
        }
        if(g.a < f.a) swap(f, g);
        if(g * mid > f * mid){
            swap(f, g);
            if(!lc) lc = new superlee;
            lc->update(l, mid, g);
        }else{
            if(!rc) rc = new superlee;
            rc->update(mid + 1, r, g);
        }
    }
    int query(int l, int r, int x){
        if(l == r) return f * x;
        if(x <= mid) return max(f * x, lc? lc->query(l, mid, x) : -inf * inf);
        else return max(f * x, rc? rc->query(mid + 1, r, x) : -inf * inf);
    }
};
array<int, 300004> A, B, S, H, dp;
array<superlee*, 1200004> seg;
void update(int p, int l, int r, int ql, int qr, line f){
    if(ql > r || qr < l) return;
    if(ql <= l && qr >= r){
        seg[p]->update(-inf, inf, f);
        return;
    }
    update(lp, l, mid, ql, qr, f);
    update(rp, mid + 1, r, ql, qr, f);
}
int query(int p, int l, int r, int c, int x){
    if(c > r || c < l) return -inf * inf;
    if(l == r) return seg[p]->query(-inf, inf, x);
    return max({seg[p]->query(-inf, inf, x), query(lp, l, mid, c, x), query(rp, mid + 1, r, c, x)});
}
int DP(int n, int k){
    update(1, 0, n, 0, k, {0, 0});
    for(int i = 1; i <= n; i++){
        dp[i] = query(1, 0, n, i, S[i]) + H[i];
        update(1, 0, n, i, i + k, {B[i] - i, dp[i] + (i - B[i]) * S[i] - H[i]});
    }
    return dp[n];
}
signed main(){
    int n, k;
    cin >> n >> k;
    for(superlee *&s : seg) s = new superlee;
    for(int i = 1; i <= n; i++){
        cin >> A[i];
        S[i] = S[i - 1] + A[i];
        H[i] = H[i - 1] + i * A[i];
    }
    for(int i = 1; i <= n; i++) cin >> B[i];
    cout << DP(n, k) << "\n";
    return 0;
}

Aliens優化

先不管 K

狀態

\(dp_{i, t} = \) 現在時間 \(i\),手中有沒有商品的最多錢

轉移

\(dp_{i, 0} = max(dp_{i - 1, 0}, dp_{i - 1, 1} + P_i)\)

\(dp_{i, 1} = max(dp_{i - 1, 1}, dp_{i - 1, 0} - P_i)\)

優化

如何限制交易次數?

課稅!

優化

如果每次交易要課稅,那麼獲利在稅金以下的交易都不會進行

優化

二分搜稅金直到交易次數恰好為 K

複雜度

\(O(NlogC)\)

#include <bits/stdc++.h>
#pragma optimize("Ofast")
#define int long long
using namespace std;
struct ooo{
    int s, c;
    ooo(){}
    ooo(int ss, int cc): s(ss), c(cc){}
    ooo operator+(ooo o){
        return ooo(s + o.s, c + o.c);
    }
};
array<int, 2000004> P;
array<array<ooo, 2>, 2000004> dp;
ooo max(ooo a, ooo b){
    if(a.s != b.s) return a.s > b.s? a : b;
    return a.c > b.c? a : b;
}
ooo DP(int n, int t){
    dp[0][0] = {0, 0};
    dp[0][1] = {-(1ll << 60), 0};
    for(int i = 1; i <= n; i++){
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + ooo(P[i], 1));
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + ooo(-P[i] + t, 0));
    }
    return dp[n][0];
}
int BIS(int n, int k){
    int l = -1e7, r = 0, mid;
    while(l != r){
        mid = (l + r) >> 1;
        if(DP(n, mid).c < k) l = mid + 1;
        else r = mid;
    }
    return DP(n, l).s - l * k;
}
signed main(){
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        cin >> P[i];
    }
    cout << BIS(n, k) << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
struct ufo{
    int v, c;
    ufo(){}
    ufo(int val, int cnt): v(val), c(cnt){}
    ufo operator+(ufo u){
        return ufo(v + u.v, c + u.c);
    }
    bool operator>(ufo u){
        return v >= u.v;
    }
};
int l, r;
array<int, 300004> A, S;
array<ufo, 300004> dp, Q;
inline ufo turn(int j, int i, int p){
    int k = (j + 1 + i) >> 1, cost = A[k] * (k - j) - (S[k] - S[j]) + (S[i] - S[k]) - A[k] * (i - k);
    return dp[j] + ufo(cost + p, 1);
}
void BIS(int t, int k){
    int mid, i, j;
    while(r >= l){
        i = Q[r].v, j = Q[r].c;
        if(t < i && turn(j, i, 0) > turn(t, i, 0)) r--;
        else break;
        k = i;
    }
    while(i != k){
        mid = (i + k) >> 1;
        if(turn(j, mid, 0) > turn(t, mid, 0)) k = mid;
        else i = mid + 1;
    }
    Q[++r] = ufo(i, t);
}
ufo DP(int n, int p){
    l = 0, r = 0;
    dp[0] = ufo(0, 0), Q[0] = ufo(0, 0);
    for(int i = 1; i <= n; i++){
        if(r > l && Q[l + 1].v <= i) l++;
        dp[i] = turn(Q[l].c, i, p);
        BIS(i, n + 1);
    }
    return dp[n];
}
int ALN(int n, int k){
    int p = 0;
    for(int i = 1ll << 60; i; i >>= 1){
        if(DP(n, p + i).c >= k) p += i;
    }
    return DP(n, p).v - p * min(DP(n, p).c, k);
}
signed main(){
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= n; i++) cin >> A[i];
    sort(A.begin() + 1, A.begin() + n + 1);
    for(int i = 1; i <= n; i++){
        S[i] = A[i] + S[i - 1];
    }
    A[n + 1] = A[n] + 1, S[n + 1] = A[n + 1] + S[n];
    cout << ALN(n, k) << "\n";
    return 0;
}

嗷嗷待補

分治優化

狀態

\(dp_{i, j} = \) 最後在 \(i\),設了 \(j\) 個郵局的答案

轉移

\(dp_{i, j} = max_{k < j}(dp_{i - 1, k} + cost(k + 1, j))\)

優化

如果 \(i \le j\),\(i\) 的轉移點 \(\le j\) 的轉移點

優化

對於 \((l, r)\) 找到 \(mid\) 的轉移點,就可以得到 \((l, mid)\) 和 \((mid + 1, r)\) 轉移點的範圍

複雜度

Before : \(O(N^2K)\)

After : \(O(NKlogN)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 1 << 10> A, S;
array<array<int, 1 << 10>, 1 << 10> C, dp;
void build(int n){
    int mid;
    for(int k = 1; k <= n; k++){
        for(int i = 1, j = k; j <= n; i++, j++){
            mid = (i + j) >> 1;
            C[i][j] += (mid - i + 1) * A[mid] - (S[mid] - S[i - 1]);
            C[i][j] += (S[j] - S[mid]) - (j - mid) * A[mid];
        }
    }
}
void DC(int l, int r, int tl, int tr, int k){
    int mid = (l + r) >> 1, t;
    dp[mid][k] = 1ll << 60;
    for(int i = tl; i <= min(mid - 1, tr); i++){
        if(dp[mid][k] > dp[i][k - 1] + C[i + 1][mid]){
            dp[mid][k] = dp[i][k - 1] + C[i + 1][mid];
            t = i;
        }
    }
    if(l == r) return;
    DC(l, mid, tl, t, k);
    DC(mid + 1, r, t, tr, k);
}
int DP(int n, int k){
    for(int i = 1; i <= k; i++) DC(i, n, i - 1, n, i);
    return dp[n][k];
}
signed main(){
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= n; i++) dp[i][0] = 1ll << 60;
    for(int i = 2; i <= n; i++) cin >> A[i];
    sort(A.begin() + 1, A.begin() + n + 1);
    for(int i = 1; i <= n; i++) S[i] = A[i] + S[i - 1];
    build(n);
    cout << DP(n, min(n, k)) << "\n";
    return 0;
}

題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 3004> X;
array<array<int, 3004>, 3004> dp;
int cost(int l, int r){
    return (X[r] - X[l]) * (X[r] - X[l]);
}
void div(int ql, int qr, int l, int r, int k){
    int t, qm = (ql + qr) >> 1;
    dp[k][qm] = 1e18;
    for(int i = l; i < min(r + 1, qm); i++){
        if(dp[k - 1][i] + cost(i, qm) < dp[k][qm]){
            t = i;
            dp[k][qm] = dp[k - 1][i] + cost(i, qm);
        }
    }
    if(ql == qr) return;
    div(ql, qm, l, t, k);
    div(qm + 1, qr, t, r, k);
}
int DP(int n, int k){
    for(int i = 1; i <= n; i++){
        dp[1][i] = X[i] * X[i];
    }
    for(int i = 2; i <= k; i++){
        div(i, n, i - 1, n, i);
    }
    return dp[k][n];
}
signed main(){
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= n; i++){
        cin >> X[i];
        X[i] += X[i - 1];
    }
    cout << DP(n, k) << "\n";
    return 0;
}

四邊形優化

區間低批

\(dp_{i, j} = min_{i \le k < j}(dp_{i, k} + dp_{k + 1, j} + f(i, j))\)

四邊形不等式

\(a \le b \le c \le d\)

\(f(b, c) \le f(a, d)\)

\(f(a, c) + f(b, d) \le f(a, d) + f(b, c)\)

性質

\(a \le b \le c \le d\)

\(dp_{a, c} + dp_{b, d} \le dp_{a, d} + dp_{b, c}\)

性質

\(l < r\)

\(t(l, r) = dp_{l, r}\) 最佳轉移點

\(t(l, r - 1) \le t(l, r) \le t(l + 1, r)\)

複雜度

\(t(l, r) = dp_{l, r}\) 最佳轉移點

\(t(l - 1, r - 1) \le t(l, r - 1) \le t(l, r) \le t(l + 1, r) \le t(l + 1, r + 1)\)

\(O(N^2)\)

另一種低批

\(dp_i = min_{j < i}(dp_j + f(j + 1, i))\)

凸性優化

\(T(i, j) = dp_j + f(j + 1, i), a \le b \le c \le d\\ if(T(b, c) \le T(a, c)) : T(b, d) \le T(a, d) \\ \to if(T(a, d) \le T(b, d) : T(a, c) \le T(b, c))\)

X O O
X X
X X X O O
X X X X
X X X X X
X X X X X X
X X X X X X X

\(if(j 為 i 最佳轉移點) : \)

X X X X
X O
X X X X X
X X X X X
X X X X X
X X X X X
X X X X X X
X X X X X X X

\(O為預期最佳轉移點\)

X O
X X O
X X X O O
X X X X
X X X X X
X X X X X X
X X X X X X X
X O
X X O
X X X
X X X X O O
X X X X X
X X X X X X
X X X X X X X

凹性優化

\(T(i, j) = dp_j + f(j + 1, i), a \le b \le c \le d\\ if(T(a,c) \le T(b, c)) : T(a, d) \le T(b, d) \\ \to if(T(b, d) \le T(a, d) : T(b, c) \le T(a, c))\)

X O O
X X
X X X O O
X X X X
X X X X X
X X X X X X
X X X X X X X

\(if(j 為 i 最佳轉移點) : \)

X X X X X
X O
X X X X X X
X X X X X X X
X X X X X X X X
X X X X X
X X X X X X
X X X X X X X

\(O為預期最佳轉移點\)

O
X O
X X O
X X X O
X X X X
X X X X X
X X X X X X
X X X X X X X
O
X
X X
X X X
X X X X O O O
X X X X X
X X X X X X
X X X X X X X

優化方法

最佳轉移點有單調性

二分搜從哪裡開始某個轉移點會比較好

\(O(NlogN)\)

題目

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 5004> X;
array<array<int, 5004>, 5004> dp, turn;
int DP(int n){
    for(int i = 1; i <= n; i++){
        dp[i][i] = 0;
        turn[i][i] = i;
    }
    for(int k = 1; k < n; k++){
        for(int i = 1, j = i + k; j <= n; i++, j++){
            dp[i][j] = 1e18;
            for(int t = turn[i][j - 1]; t <= turn[i + 1][j]; t++){
                if(dp[i][t] + dp[t + 1][j] < dp[i][j]){
                    turn[i][j] = t;
                    dp[i][j] = dp[i][t] + dp[t + 1][j];
                }
            }
            dp[i][j] += X[j] - X[i - 1];
        }
    }
    return dp[1][n];
}
signed main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> X[i];
        X[i] += X[i - 1];
    }
    cout << DP(n) << "\n";
    return 0;
}

#include <bits/stdc++.h>
#define int long long
using namespace std;
struct dot{
    int x, y;
    bool operator<(dot d){
        return x < d.x;
    }
    int operator*(dot d){
        return (d.x - x) * (d.y - y);
    }
};
array<dot, 100004> up, down;
int solve(int ul, int ur, int dl, int dr){
    int dmid = (dl + dr) / 2, umid, ans = 0;
    for(int i = ul; i <= ur; i++){
        if(down[dmid] < up[i]){
            if(down[dmid] * up[i] > ans){
                ans = down[dmid] * up[i];
                umid = i;
            }
        }
    }
    if(dl == dr) return ans;
    return max({ans, solve(ul, umid, dl, dmid), solve(umid, ur, dmid + 1, dr)});
}
signed main(){
    int n, m, x = 0, y = 0, z;
    cin >> n;
    for(int i = 0; i < n; i++){
        cin >> z;
        if(i & 1) y -= z;
        else{
            x += z;
            up[i / 2] = {x, y};
        }
    }
    x = y = 0;
    cin >> m;
    for(int i = 0; i < m; i++){
        cin >> z;
        if(i & 1) x += z;
        else{
            y -= z;
            down[i / 2] = {x, y};
        }
    }
    cout << solve(0, n / 2 - 1, 0, m / 2 - 1);
    return 0;
}

#include <bits/stdc++.h>
#define int long long
using namespace std;
array<int, 3004> C;
array<array<int, 3004>, 3004> dis, cst, turn, dp;
void DIS(int n){
    for(int i = 1; i <= n; i++){
        for(int j = i - 1; j > 0; j--){
            dis[i][j] = (i - j) * C[j] + dis[i][j + 1];
        }
        for(int j = i + 1; j <= n; j++){
            dis[i][j] = (j - i) * C[j] + dis[i][j - 1];
        }
    }
    for(int i = 1; i <= n; i++){
        cst[i][i] = 0;
        turn[i][i] = i;
    }
    for(int k = 1; k < n; k++){
        for(int i = 1, j = i + k; j <= n; i++, j++){
            cst[i][j] = 1e18;
            for(int t = turn[i][j - 1]; t <= turn[i + 1][j]; t++){
                if(dis[t][i] + dis[t][j] < cst[i][j]){
                    cst[i][j] = dis[t][i] + dis[t][j];
                    turn[i][j] = t;
                }
            }
        }
    }
}
void div(int ql, int qr, int l, int r, int k){
    int t, qm = (ql + qr) >> 1;
    dp[k][qm] = 1e18;
    for(int i = l; i < min(r + 1, qm); i++){
        if(dp[k][qm] > dp[k - 1][i] + cst[i + 1][qm]){
            dp[k][qm] = dp[k - 1][i] + cst[i + 1][qm];
            t = i;
        }
    }
    if(ql == qr) return;
    div(ql, qm, l, t, k);
    div(qm + 1, qr, t, r, k);
}
int DP(int n, int k){
    for(int i = 1; i <= n; i++){
        dp[1][i] = cst[1][i];
    }
    for(int i = 2; i <= k; i++){
        div(i, n, i - 1, n, i);
    }
    return dp[k][n];
}
signed main(){
    int n, k;
    cin >> n >> k;
    for(int i = 1; i <= n; i++) cin >> C[i];
    DIS(n);
    cout << DP(n, k) << "\n";
    return 0;
}

低批

By thanksone

低批

  • 1,004