题目

P3147 [USACO16OPEN] 262144 P

算法标签: 动态规划, 线性 d p dp dp, 贪心, 区间 d p dp dp

思路

定义状态表示 f [ i ] [ j ] f[i][j] f[i][j]表示左边界是 i i i并且合并出的最大值是 j j j的右边界是什么位置(开区间), k k k区间覆盖的是 [ i , f [ i ] [ j ] − 1 ] [i, f[i][j] - 1] [i,f[i][j]1], 如何进行状态转移?
如果当前状态 f [ i ] [ k ] = 0 f[i][k] = 0 f[i][k]=0, 可以尝试合并两个 k − 1 k - 1 k1达到当前状态, 设 r = f [ i ] [ k − 1 ] r = f[i][k - 1] r=f[i][k1], 那么 f [ i ] [ k ] = f [ r ] [ k − 1 ] f[i][k] = f[r][k - 1] f[i][k]=f[r][k1], 如果最后计算出 f [ i ] [ k ] ≠ 0 f[i][k] \ne 0 f[i][k]=0那么可以记录到答案中, 状态转移依赖 f [ i ] [ k − 1 ] f[i][k - 1] f[i][k1], 可以对 k k k从小到大枚举, 递推就能计算出当前状态, 算法时间复杂度 O ( n m ) O(nm) O(nm)

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 270010, M = 60;

int n, ans, f[N][M];

int main() {
   
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n;
    for (int i = 1; i <= n; ++i) {
   
        int val;
        cin >> val;
        f[i][val] = i + 1;
    }

    //以i为左端点合并出数字j的右端点是f[i][j]
    for (int k = 2; k < M; ++k) {
   
        for (int i = 1; i <= n; ++i) {
   
            if (!f[i][k]) f[i][k] = f[f[i][k - 1]][k - 1];
            if (f[i][k]) ans = k;
        }
    }

    cout << ans << "\n";
    return 0;
}

为什么 M = 60 M = 60 M=60

注意到 N = 262144 N = 262144 N=262144, 也就是 2 18 2 ^ {18} 218, 每个数的范围不超过 40 40 40, 最大的结果就是 40 + 18 = 58 40 + 18 = 58 40+18=58, 向上取整就是 60 60 60