[題解]CF958C3 Encryption (hard)

WaterSunHB發表於2024-07-24

思路

先考慮 \(\Theta(n^2k)\) 的暴力 DP。

定義 \(dp_{i,j}\) 表示在前 \(i\) 個數中選取 \(j\) 個的最小和,轉移顯然:

\[dp_{i,j} = \min_{1 \leq k < i}\{dp_{k,j - 1} + s_{k + 1,i} \bmod p\} \]

注意到一個性質:\(dp_{i,j} \equiv s_i \pmod p\)。因為前者是前 \(i\) 項分為若干段的模 \(p\) 下的和,與後者顯然在模 \(p\) 意義下同餘。

對於 \(dp_{i,j}\) 的兩個轉移的位置 \(k_1,k_2\),計算出的結果分別是,\(dp_{k_1,j - 1} + s_{k_1 + 1,i} \bmod p\)\(dp_{k_2,j - 1} + s_{k_2 + 1,i} \bmod p\)。並且有:

\[dp_{k_1,j - 1} + s_{k_1 + 1,i} \bmod p \equiv dp_{k_2,j - 1} + s_{k_2 + 1,i} \bmod p \pmod p \]

假設 \(dp_{k_1,j - 1} < dp_{k_2,j - 1}\),由於 \(s_{k_1 + 1,i} \bmod p\)\(s_{k_2 + 1,i} \bmod p\) 一定小於 \(p\),所以前者一定小於後者。

這樣,對於每一個 \(j\),記錄最小的 \(dp_{i,j}\) 即可。

Code

#include <bits/stdc++.h>
#define re register
#define int long long

using namespace std;

const int N = 5e5 + 10,M = 110;
int n,m,p;
int arr[N],s[N];
int dp[N][M],f[M];

inline int read(){
    int r = 0,w = 1;
    char c = getchar();
    while (c < '0' || c > '9'){
        if (c == '-') w = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9'){
        r = (r << 3) + (r << 1) + (c ^ 48);
        c = getchar();
    }
    return r * w;
}

#define sum(l,r) ((s[r] - s[l - 1]) % p)

signed main(){
    n = read(),m = read(),p = read();
    for (re int i = 1;i <= n;i++) s[i] = s[i - 1] + (arr[i] = read());
    for (re int i = 1;i <= n;i++){
        for (re int j = 1;j <= min(i,m);j++) dp[i][j] = dp[f[j - 1]][j - 1] + sum(f[j - 1] + 1,i);
        for (re int j = 1;j <= min(i,m);j++){
            if (!f[j] || dp[f[j]][j] > dp[i][j]) f[j] = i;
        }
    }
    printf("%lld",dp[n][m]);
    return 0;
}

相關文章