DP 练习集合

A. POJ 1923 Fourier's Lines

题意简述

试判断 n 条任意(不重合)直线是否可能形成 m 个交点,满足任意一个交点只有两条直线经过?如果是,并输出此种情况下,平面最多能被切成几份。

解题报告

直线之间产生交点当且仅当直线斜率不同
所以对于 \(n\) 条已经确定的直线,先把其中一类斜率全相同的直线拿出来,记有 \(i\) 条,那么剩下 \((n - i)\) 条直线的斜率一定和那 \(i\) 条都不相同,每一条都能贡献 \(i\) 个交点(一个交点只有两条直线经过),它们产生的贡献就是 \(i(n - i)\);而剩下的 \((n - i)\) 条直线内部又可以产生贡献,递归计算即可

实现是记忆化搜索

代码实现

int n, m;
char dp[100 + 10][10000 + 10]; // i 条线 j 个交点 是否存在方案

char dfs(int n, int m) {
    // n 条直线里拿出一类斜率相同的直线共 i 条,剩下 (n - i) 条直线
    // 交点个数为 i * (n - i) 加上 (n - i) 条直线内部形成的交点个数
    if (m < 0 || m > ((n * (n - 1)) >> 1)) return 0;
    if (!m) return 1;
    if (dp[n][m] != -1) return dp[n][m];
    for (int i = 1; i <= n - 1; ++i) {
        if (dfs(n - i, m - i * (n - i))) 
            return dp[n][m] = 1;
    } return dp[n][m] = 0;
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int cas = 0;
    while (true) {
        cin >> n >> m;
        if (n == 0 && m == 0) break;
        memset(dp, -1, sizeof dp);
        cout << "Case " << ++cas << ": ";
        if (dfs(n, m)) cout << n << " lines with exactly " << m << " crossings can cut the plane into " << n + m + 1 << " pieces at most." << endl;
        else cout << n << " lines cannot make exactly " << m << " crossings." << endl;
    }
    return 0;
}

C. Increasing Sequences

题意简述

给定一个数字串,现在要给这个串添加逗号(也可以不加),使之成为一个严格递增的数字序列,并且最小化最后一个数字的长度。如果可以,最大化第一个数字的长度。输出方案。

解题报告

首先,最小化最后一个数字的长度意味着你要使每一段的长度都尽可能小,这和最大化第一个数字的长度是冲突的。

所以我们先不管第二个要求,设 \(f[i]\) 表示以第 \(i\) 个数字为结尾的最后一段,能获得的最大长度,也就是说 \(f[n]\) 就是最优答案。

注意到 DP 的一个特点:无后效性,无论我们之前是走的哪条路得到的这个答案,都不会影响后续的求解。因此,我们基于这个状态进行第二阶段的 DP。

\(g[i]\) 表示以 \(i\) 为开头的那一段的长度,注意此时的最优子结构是最大长度。显然,初始状态是 \(g[n - f[n] + 1] = f[n]\),我们要对 \(g\) 进行递推求解。

递推方程 \(f\)\(g\) 都是一样的,枚举接下来一段的长度(或者端点),判断,更新答案

输出方案就根据 \(g\) 数组挨个输出即可

代码实现

const int MAXLEN = 80 + 10;

int n, aa[MAXLEN];
int dp[MAXLEN];

struct Seq {
    int l, r; Seq(int _l = 0, int _r = 0) : l(_l), r(_r) {}
};

bool operator > (const Seq &a, const Seq &b) {
    int al = a.l, ar = a.r, bl = b.l, br = b.r;
    while (aa[al] == 0 && al <= ar) ++al;
    while (aa[bl] == 0 && bl <= br) ++bl;
    int la = ar - al + 1, lb = br - bl + 1;
    if (la < lb) return false;
    else if (la > lb) return true;
    else {
        for (int i = 1; i <= la; ++i) {
            int xa = al + i - 1, xb = bl + i - 1;
            if (aa[xa] > aa[xb]) return true;
            else if (aa[xa] < aa[xb]) return false;
        }
    } return false;
}

void cleanup() {
    memset(aa, 0, sizeof aa); memset(dp, 0, sizeof dp);
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    std::string input;
    while (cin >> input) {
        if (input.size() == 1 && input[0] == '0') break;
        n = (int) input.size();
        rep (i, 1, n) aa[i] = input[i - 1] - '0';
        
        for (int i = 1; i <= n; ++i) {
            dp[i] = i; // 此时,dp[i] 表示以第 i 个位置结尾的上一段长度
            for (int j = i - 1; j >= 1; --j) {
                if (Seq(j + 1, i) > Seq(j - dp[j] + 1, j)) {
                    dp[i] = i - j; break;
                }
            }
        }
        int lastlen = dp[n]; memset(dp, 0, sizeof dp);
        dp[n - lastlen + 1] = lastlen; // 以刚刚求出的最优解为基础,倒着 dp
        // 此时,dp[i] 表示以第 i 个位置为开头的这一段长度
        for (int i = n - lastlen; i >= 1; --i) {
            if (aa[i] == 0) dp[i] = dp[i + 1] + 1;
            else for (int j = n - lastlen + 1; j > i; --j) {
                if (Seq(j, j + dp[j] - 1) > Seq(i, j - 1)) {
                    dp[i] = j - i; break;
                }
            }
        } 
        for (int i = 1, nowlen = dp[1] + 1; i <= n; ++i) {
            if (i == nowlen) {
                cout << ','; nowlen = i + dp[i];
            }
            cout << aa[i];
        } cout << '\n';
        cleanup();
    }
    return 0;
}

E. Strange Towers of Hanoi

题意简述

一个四塔的汉诺塔。

解题报告

子问题:枚举拿出多少盘子,将它们以四塔的操作方式放到另一个塔上
剩下的盘子以三塔的操作方式放到目标塔上,最后再把另一个塔上的盘子以四塔的操作方式放上来就行了

可以用记搜写,也可以递推

代码实现

int dp[13];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    dp[1] = 1; cout << 1 << endl;
    for (int i = 2; i <= 12; ++i) {
        dp[i] = 0x7f7f7f7f;
        for (int j = 1; j < i; ++j) {
            // 枚举 j 个盘子放到另一个塔,最后放回来,这是一个已知答案的四塔问题
            // 剩下的 i - j 个盘子是三塔问题
            dp[i] = std::min(dp[i], 2 * dp[j] + ((1 << (i - j)) - 1));
        }
        cout << dp[i] << endl;
    }
    return 0;
}

F. Stock Exchange

最长上升子序列

const int MAXN = 100000 + 10;

int n;
int d[MAXN], len;

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    while (cin >> n) {
        len = 0;
        for (int i = 1; i <= n; ++i) {
            int x; cin >> x;
            if (i == 1) { d[++len] = x; continue; } 
            if (x > d[len]) d[++len] = x;
            else {
                int j = (std::lower_bound(d + 1, d + len + 1, x) - d);
                d[j] = x;
            }
        } cout << len << endl;
    }
    return 0;
}

G. Fixed Points

题意简述

一个长度为 \(n\) 的序列 \(a\),现在可以删除一些数字(也可以不删),删完之后右面的数字编号会自动 \(- 1\),求问最少删除几个数字可以让这个序列中 \(a_i = i\) 的数字个数超过 \(k\),或判断无解,

解题报告

dp[i][j] 表示考虑前 \(i\) 个数字,删除 \(j\) 个数字后,\(a_i = i\) 最多有多少个。

对当前数字删或不删分别转移即可

代码实现

const int MAXN = 2000 + 10;

int n, k;
int aa[MAXN];
int dp[MAXN][MAXN]; // 前 i 个数字,删去 j 个

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T;
    while (T --> 0) {
        cin >> n >> k;
        rep (i, 1, n) cin >> aa[i];
        memset(dp, 0, sizeof dp);
        rep (i, 1, n) {
            rep (j, 0, i) {
                // 删与不删
                dp[i][j] = std::max(dp[i - 1][std::max(0, j - 1)], dp[i - 1][j] + (aa[i] == i - j));
            }
        }
        int ans = -1; rep (i, 0, n) {
            if (dp[n][i] >= k) { ans = i; break; }
        } cout << ans << endl;
    }
    return 0;
}

H. Equidistant Vertices

题意简述

一棵 \(n\) 个点的树,从中选择恰好 \(k\) 个点使得两两距离相同,求方案数。

解题报告

\(k = 2\) 时答案就是 \(\frac{n(n - 1)}{2}\)
\(k \geq 3\) 时,有一个结论:

必定存在一个点 \(C\),满足这个点集里的所有点到 \(C\) 的距离相等。

于是就考虑枚举这个点 \(C\),提起来当根,可选的点也就是同一深度的点;但是如果两个点 LCA 不是根的话就出问题了。

所以设 \(dp[i][j]\) 表示根的前 \(i\) 棵子树中,有 \(j\) 棵子树是贡献了一个点的,这种情况的方案数

转移就对当前子树选或不选分别转移即可

代码实现

const int MAXN = 100 + 10;
const int HA = 1e9 + 7;

int n, k; int ans;
std::vector<int> G[MAXN];
int siz[MAXN][MAXN]; // siz[u][dep(from 0)] : u 子树的第 dep 层有几个节点
int dp[MAXN][MAXN]; // 以 root 为根时,前 i 个子树中有 j 个子树贡献了点

void cleanup() {
    ans = 0; rep (i, 1, n) G[i].clear();
}
void dfs(int u, int fa) {
    siz[u][0] = 1;
    forall (G[u], i) {
        int v = G[u][i];
        if (v == fa) continue;
        dfs(v, u);
        rep (j, 1, n) siz[u][j] += siz[v][j - 1];
        // 直接循环到 n 其实没啥问题
    }
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T; while (T --> 0) {
        cin >> n >> k;
        rep (i, 1, n - 1) { 
            int u, v; cin >> u >> v;
            G[u].push_back(v); G[v].push_back(u);
        }
        if (k == 2) { cout << (n * (n - 1) >> 1) << endl; cleanup(); continue;}
        rep (root, 1, n) {
            dfs(root, 0);
            rep (dep, 1, n) {
                std::vector<int> sizes;
                forall (G[root], i) {
                    int u = G[root][i];
                    sizes.push_back(siz[u][dep - 1]);
                }
                dp[0][0] = 1;
                rep (x, 1, (int) sizes.size()) {
                    dp[x][0] = 1;
                    rep (d, 1, x) {
                        // 转移:选与不选
                        dp[x][d] = (dp[x - 1][d] + 1ll * dp[x - 1][d - 1] * sizes[x - 1] % HA) % HA;
                    }
                } ans = (ans + dp[sizes.size()][k]) % HA;
                memset(dp, 0, sizeof dp);
                sizes.clear();
            } memset(siz, 0, sizeof siz);
        } cout << ans << endl;
        cleanup();
    }
    return 0;
}

I. Unstable String

题意简述

定义一个 01 字符串是美丽的,当且仅当它所有相邻两位是不同的

现在给定一个只含 01? 的字符串,? 可以替换成 01,求这个字符串最多有多少个子串是美丽的。

解题报告

考虑 dp。

f[i][0/1] 表示以第 \(i\) 位为结尾的极长美丽子串的长度,且第 \(i\) 位是 \(0/1\)

转移对于 \(0\)\(1\) 分别转移

统计答案只需要把每一个 \max(f[i][0], f[i][1]) 加起来就好了

代码实现

const int MAXN = 2e5 + 10;

int n;
std::string s;
long long int dp[MAXN][2]; // dp[i][0/1] 以 i 为结尾,第 i 位是 0 / 1 的极长子串长度

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T; while (T --> 0) {
        cin >> s; n = (int) s.size(); s = " " + s;
        rep (i, 1, n) {
            if (s[i] == '0' || s[i] == '?') dp[i][0] = dp[i - 1][1] + 1;
            if (s[i] == '1' || s[i] == '?') dp[i][1] = dp[i - 1][0] + 1;
        }
        long long int ans =  0;
        rep (i, 1, n) ans += std::max(dp[i][0], dp[i][1]);
        cout << ans << endl;
        memset(dp, 0, sizeof dp);
    }
    return 0;
}

L. Parsa's Humongous Tree

题意简述

一棵树,每个点的点权范围是 \([l, r]\),一条边的边权为两个点点权之差绝对值,求整棵树的最大边权和是多少

解题报告

首先证明一个重要结论:每个点的点权只能是 \(l\)\(r\)

假设一个点周围的点权都已经确定,将这些权值放到一个数轴上,题目就转化成了找一个点到这些点距离之和最大。显然是距离中位数越远越好,也就是一定会取两个极端值。

之后就是普通 dp 了。设 \(f[i][0/1]\) 表示 \(i\) 点权值为 \(l(0),r(1)\)\(i\) 为根的子树的最大答案。分别转移即可。

代码实现

const int MAXN = 2e5 + 10;

int n;
long long int dp[MAXN][2]; // 以 i 为根的子树,a[i] = l(0), r(1)
std::vector<int> G[MAXN];
std::pair<int, int> rgs[MAXN];

void dfs(int u, int fa) {
    forall (G[u], i) {
        int v = G[u][i];
        if (v == fa) continue;
        dfs(v, u);
        dp[u][0] += std::max(dp[v][0] + abs(rgs[u].first - rgs[v].first), dp[v][1] + abs(rgs[u].first - rgs[v].second));
        dp[u][1] += std::max(dp[v][0] + abs(rgs[u].second - rgs[v].first), dp[v][1] + abs(rgs[u].second - rgs[v].second));
    }
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T;
    while (T --> 0) {
        cin >> n;
        rep (i, 1, n) cin >> rgs[i].first >> rgs[i].second;
        rep (i, 1, n - 1) {
            int u, v; cin >> u >> v;
            G[u].push_back(v); G[v].push_back(u);
        } dfs(1, 0);
        cout << std::max(dp[1][0], dp[1][1]) << endl;
        memset(dp, 0, sizeof dp);
        rep (i, 1, n) G[i].clear();
    }
    return 0;
}

N. Armchairs

题意简述

人和椅子一共 n 个,编号从 1 到 n,其中有不超过 n/2 个人。现在要求每个人选一把椅子坐下,代价是编号之差的绝对值,求最小代价和。

解题报告

首先把人和椅子拿出来分别排序。

dp[i][j] 表示前 i 个人坐前 j 个椅子的最小代价。
转移就对第 i 个人坐、不坐第 j 个椅子分别转移即可

统计答案的时候把 dp[人数][1~椅子数] 都统计一下即可

代码实现

const int MAXN = 5000 + 10;

int n;
std::vector<int> mx[2];
std::vector<int> &pp = mx[1], &chr = mx[0];
int dp[MAXN][MAXN]; // 前 i 个人坐前 j 把椅子

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    rep (i, 1, n) {
        int f; cin >> f; mx[f].push_back(i);
    } memset(dp, 0x3f, sizeof dp);
    rep (i, 0, (int) chr.size()) dp[0][i] = 0;
    rep (i, 1, (int) pp.size()) {
        rep (j, 1, (int) chr.size()) {
            dp[i][j] = std::min(dp[i][j - 1], dp[i - 1][j - 1] + abs(mx[1][i - 1] - mx[0][j - 1]));
        }
    } int ans = 0x3f3f3f3f;
    rep (i, 1, (int) chr.size()) {
        ans = std::min(ans, dp[pp.size()][i]);
    } cout << ans << endl;
    return 0;
}

O. Phoenix and Computers

题意简述

现在有一排 n 台电脑,开启第 i 台和第 i + 2 台会自动开启第 i + 1 台。求开启全部电脑的方案数。

解题报告

dp。

观察到开启电脑的序列一定呈这样分布:

=为自动开启,|为手动开启
|||||=||||=||||=||||......

也就是,手动开启的是成段分布的,这就是一个子问题

设 f[i][j] 表示开启了前 i 台电脑,其中手动开启了 j 台电脑,且这 i 台电脑的一个后缀都手动打开(显然最后一个必须手动打开,因为右边没有电脑),的方案数

考虑打开第 i + 2 台电脑往后的一串电脑,使第 i + 1 台电脑自动打开。这里计算方案数需要两个值:手动打开往后一串电脑的方案数,把这一串电脑的打开序列合并进以前打开序列的方案数。

结论:手动打开一串长为 \(n\) 的电脑的方案数是 \(2^{n - 1}\)

枚举最先打开第 \(i\) 台电脑,接下来就是往序列里剩下的 \(n - 1\) 个空依次填放右边的 \(n - i\) 台电脑,剩下的空就用来填左边的,方案数即为 \(C_{n - 1}^{n - 1}\)

也就是 \(\sum_{i = 1}^n C_{n - 1}^{n - i} = 2^{n - 1}.\)

结论:按顺序将一个长为 \(n\) 的序列合进一个长为 \(m\) 的序列的方案数是 \(C_{n + m}^n.\)

可以直接隔板法。\(n + m\) 个空里选 \(n\) 个。

代码实现

const int MAXN = 400 + 10;
int HA = 0;

int n;
int dp[MAXN][MAXN]; // 开启了前 i 台电脑,其中有 j 台电脑是手动开的,且第 i 台一定是手动开的

int fac[MAXN], invfac[MAXN], twoi[MAXN];

int fastPow(int a, int b, int p) {
    int ret = 1; while (b) {
        if (b & 1) ret = 1ll * ret * a % p;
        a = 1ll * a * a % p;
        b >>= 1;
    } return ret;
}

int C(int n, int m) { return 1ll * fac[n] * invfac[m] % HA * invfac[n - m] % HA; }

int main() {
    n = read(); HA = read();
    
    twoi[0] = fac[0] = 1; rep (i, 1, MAXN - 1) fac[i] = 1ll * fac[i - 1] * i % HA, twoi[i] = 1ll * twoi[i - 1] * 2 % HA;
    invfac[MAXN - 1] = fastPow(fac[MAXN - 1], HA - 2, HA);
    for (int i = MAXN - 2; i >= 1; --i) invfac[i] = 1ll * invfac[i + 1] * (i + 1) % HA;

    for (int i = 1; i <= n; ++i) {
        dp[i][i] = twoi[i - 1];
        for (int j = 0; j <= i; ++j) {
            for (int k = 1; i + k + 1 <= n; ++k) {
                // 手动开启 i + 2 ~ i + k + 1,共 k 台电脑,使其自动开启第 i + 1 台电脑
                // twoi[k - 1] 是开启 k 台电脑的方案数
                // C(j + k, j) 是把这 k 台电脑开启序列合并进前 j 台电脑开启序列的方案数
                dp[i + k + 1][j + k] = (1ll * dp[i + k + 1][j + k] + 1ll * dp[i][j] * twoi[k - 1] % HA * C(j + k, k) % HA) % HA;
            }
        }
    } int ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans = (1ll * ans + dp[n][i]) % HA;
    } printf("%d\n", ans);
    return 0;
}

P. Maximum Sum of Products

题意简述

两个序列 \(a,b\),现在可以翻转至多一个子串,求最大的 \(\sum_{i = 1}^n a_i \times b_i.\)

解题报告

这题没有最优子结构,但可以用类似 dp 的思想解决,一个区间的递推

\(f[i][j]\) 表示翻转 \([i, j]\) 的答案,根据区间 dp 的一般套路,考虑枚举区间中点和长度(对奇数和偶数分别转移),很容易写出转移方程:

\[f[l][r] = f[l + 1][r - 1] - a_lb_l - a_rb_r + a_lb_r + a_rb_l \]

预处理:显然,初始状态是 \(f[i][i]\)\(f[i][i + 1]\),所有状态都是这些状态转移出来的,预处理显然不是难事

最后统计一遍 \(\max\{f_{i,j}\}\)

代码实现

const int MAXN = 5000 + 10;

int n;
lli aa[MAXN], bb[MAXN];
lli dp[MAXN][MAXN];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    rep (i, 1, n) cin >> aa[i];
    rep (i, 1, n) cin >> bb[i];
    for (int i = 1; i <= n; ++i) {
        dp[1][1] += aa[i] * bb[i];
    } for (int i = 2; i <= n; ++i) dp[i][i] = dp[i - 1][i - 1];
    for (int i = 1; i <= n - 1; ++i) {
        dp[i][i + 1] = dp[i][i] 
        - aa[i] * bb[i] 
        - aa[i + 1] * bb[i + 1] 
        + aa[i] * bb[i + 1] 
        + aa[i + 1] * bb[i];
    }
    for (int mid = 1; mid <= n; ++mid) {
        for (int len = 2; len <= n; ++len) {
            int l = mid - len + 1, r = mid + len - 1;
            if (l < 1 || r > n) break;
            dp[l][r] = dp[l + 1][r - 1] 
            - aa[l] * bb[l] - aa[r] * bb[r]
            + aa[l] * bb[r] + aa[r] * bb[l];
        }
    }
    for (int mid = 1; mid <= n; ++mid) {
        for (int len = 2; len <= n; ++len) {
            int l = mid - len, r = mid + len - 1;
            if (l < 1 || r > n) break;
            dp[l][r] = dp[l + 1][r - 1] 
            - aa[l] * bb[l] - aa[r] * bb[r]
            + aa[l] * bb[r] + aa[r] * bb[l];
        }
    } lli ans = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = i; j <= n; ++j) {
            ans = std::max(ans, dp[i][j]);
            // printf("%lld%c", dp[i][j], j == n ? '\n' : ' ');
        }
    } cout << ans << endl;
    
    return 0;
}

1. HDU 6968 I love exam

题意简述

\(n\) 科的考试前有 \(t\) 天时间用于复习,给定 \(m\) 本书,每本书可以让它对应的科目成绩提高一定值,同时会花费一定天数(同一天只能读同一本书,每本书只能读一次)。
一个科目挂科的充要条件是它的成绩 \(< 60\)。假设你在不复习的情况下每科都只有 0 分,请问有没有一种复习的方案,使得挂科的科目数小于 \(p\),且总成绩最高?如果有,输出总成绩;如果没有,输出 -1.

解题报告

又是一个两次 DP 的题目。

第一次 DP:设 \(f[i][j]\) 表示第 \(i\) 科要拿到 \(j\) 分最少花费多少时间。一个很简单的背包。

第二次 DP:设 \(g[i][j][k]\) 表示前 \(i\) 科,花费 \(j\) 天来复习,最终有 \(k\) 科挂科,能获得的最大成绩。转移其实和背包也差不多,对挂不挂科分别处理,用 \(f\) 数组作为背包的数据。

代码实现

const int MAX_UP = 200;
const int MAXN = 50 + 10;
const int MAXT = 500 + 10;
const int MAXM = 15000 + 10;
const int MAXSCR = 200 + 10;

int n, m, t, p;
std::map<std::string, int> idx;
int f[MAXN][MAXSCR]; // f[i][j] 表示第 i 课考到分数为 j 最少需要多少时间,是个背包
int g[MAXN][MAXT][5]; // g[i][j][k] 表示前 i 门课,前 j 天,挂了 k 课,获得的最高成绩

struct E { 
    int id, impscr, day; 
    E(int _i = 0, int _p = 0, int _d = 0) : id(_i), impscr(_p), day(_d) {}
} es[MAXM];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T; cin >> T;
    while (T --> 0) {
        cin >> n;
        rep (i, 1, n) {
            std::string s; cin >> s;  idx[s] = i;
            for (int j = 0; j <= MAX_UP; ++j) f[i][j] = 0x3f3f3f3f;
            f[i][0] = 0;
        }
        cin >> m;
        rep (i, 1, m) {
            std::string s; cin >> s; es[i].id = idx[s];
            cin >> es[i].impscr >> es[i].day;
        }
        cin >> t >> p;
        
        for (int i = 1; i <= m; ++i) {
            int id = es[i].id;
            for (int j = MAX_UP; j >= es[i].impscr; --j) {
                f[id][j] = std::min(f[id][j], f[id][j - es[i].impscr] + es[i].day);
            }
        }

        for (int i = 1; i <= n; ++i) {
            for (int k = 0; k <= std::min(i, p); ++k) {
                if (k == 0) {
                    for (int sc = MAX_UP; sc >= 60; --sc) {
                        for (int j = t; j >= f[i][sc]; --j)  {
                            g[i][j][k] = std::max(g[i][j][k], g[i - 1][j - f[i][sc]][k] + std::min(sc, 100));
                        }
                    }
                } else {
                    for (int sc = MAX_UP; sc >= 0; --sc) {
                        for (int j = t; j >= f[i][sc]; --j) {
                            g[i][j][k] = std::max(g[i][j][k], g[i - 1][j - f[i][sc]][k - (sc < 60)] + std::min(sc, 100));
                        }
                    }
                }
            }
        }

        int ans = 0;
        for (int i = 1; i <= t; ++i) {
            for (int j = 0; j <= p; ++j) {
                ans = std::max(ans, g[n][i][j]);
            }
        }
        if (ans >= (n - p) * 60) cout << ans << endl;
        else cout << -1 << endl;
        
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= t; ++j) {
                for (int k = 0; k <= p; ++k) {
                    g[i][j][k] = 0;
                }
            }
        }
        idx.clear();
    }
    return 0;
}
posted @ 2021-08-21 10:05  Handwer  阅读(43)  评论(0编辑  收藏  举报