Codeforces Round #380 记忆化搜索F. Financiers Game DP好题

F. Financiers Game
time limit per test
2 seconds
memory limit per test
512 megabytes
input
standard input
output
standard output

This problem has unusual memory constraint.

At evening, Igor and Zhenya the financiers became boring, so they decided to play a game. They prepared n papers with the income of some company for some time periods. Note that the income can be positive, zero or negative.

Igor and Zhenya placed the papers in a row and decided to take turns making moves. Igor will take the papers from the left side, Zhenya will take the papers from the right side. Igor goes first and takes 1 or 2 (on his choice) papers from the left. Then, on each turn a player can take k or k + 1 papers from his side if the opponent took exactly k papers in the previous turn. Players can't skip moves. The game ends when there are no papers left, or when some of the players can't make a move.

Your task is to determine the difference between the sum of incomes on the papers Igor took and the sum of incomes on the papers Zhenya took, assuming both players play optimally. Igor wants to maximize the difference, Zhenya wants to minimize it.

Input

The first line contains single positive integer n (1 ≤ n ≤ 4000) — the number of papers.

The second line contains n integers a1, a2, ..., an ( - 105 ≤ ai ≤ 105), where ai is the income on the i-th paper from the left.

Output

Print the difference between the sum of incomes on the papers Igor took and the sum of incomes on the papers Zhenya took, assuming both players play optimally. Igor wants to maximize the difference, Zhenya wants to minimize it.

Examples
input
3
1 3 1
output
4
input
5
-1 -2 -1 -2 -1
output
0
input
4
-4 -2 4 5
output
-13
Note

In the first example it's profitable for Igor to take two papers from the left to have the sum of the incomes equal to 4. Then Zhenya wouldn't be able to make a move since there would be only one paper, and he would be able to take only 2 or 3..

 题目大意:有一个序列ai, Igor从左往右取数, Zhenya从右往左取数。Igor先取1~2个数。两个人轮流取数,每个人取数为k~k+1,k为上一个人取数的个数。如果剩下的序列的个数小于上一个人取数的个数或者无法取数时游戏结束。Igor这个人想让取的数的和尽量大,Zhenya则想让其尽量小。问你最后的结果是什么。

题目解析:因为每次取数都要最优,所以要用dp搞。设dp数组为dp[l][r][pre][cur]分别为已经取到左端点,已经取到右端点,上一个人取的数,目前取数的人。

设sum[]记录序列的前缀和便于得到要取区间的值

设slove为计算(l, r, pre, cur)的函数

对于cur = 0(Igor)

answer = max(   slove(l + pre, r, pre, 1) + sum[l + pre - 1] - sum[l - 1],

                 slove(l + pre + 1, r, pre + 1, 1) + sum[l + pre] - sum[l - 1])  )

同理可以得到cur = 1的slove计算函数

那么就得到了一个错误的代码,因为状态空间要开l*r*pre*2 = 4000*4000*100*2

#include <bits/stdc++.h>

using namespace std;
int dp[400][401][101][2];
bool vis[401][401][101][2];
int n;
int sum[401];
int slove(int l, int r, int pre, int cur) {
    int &ret = dp[l][r][pre][cur];
    if(vis[l][r][pre][cur]) return ret;
    vis[l][r][pre][cur] = true;
    if(r - l + 1 < pre) return ret = 0;
    if(cur == 0) {
        ret = slove(l + pre, r, pre, 1) + sum[l + pre - 1] - sum[l - 1];
        if(r - l >= pre) {
            ret = max(ret, slove(l + pre + 1, r, pre + 1, 1) + sum[l + pre] - sum[l - 1]);
        }
    }else {
        ret = slove(l, r - pre, pre, 0) - sum[r] + sum[r - pre];
        if(r - l >= pre) {
            ret = min(ret, slove(l, r - pre - 1, pre + 1, 0) - sum[r] + sum[r - pre - 1]);
        }
    }
    return ret;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        sum[i] = sum[i-1] + x;
    }
    memset(vis, false, sizeof(vis));
    printf("%d
", slove(1, n, 1, 0));
    return 0;
}
View Code

因为(1+2+3+4+....+k) * 2= 4000, 操作最大值pre不超过70

当I和Z同时操作次数相同的时候,我们会发现Z的操作数要大于等于I的操作次数

所以我们可以用一个值cnt表示Z比I多操作的次数,所以r可以通过n, l, cnt三个数来求得

假设Z比I每次都多操作一次即

I:(1 + 2 + 3 + 4 +....n) + (2 + 3 + 4 + 5+....+n+1) = 4000

cnt = n, cnt的值也不会超过100

#include <bits/stdc++.h>

using namespace std;
int dp[4001][100][100][2], sum[4001], n;
bool vis[4001][100][100][2];
int slove(int l, int cnt, int pre, int cur) {
    if(vis[l][cnt][pre][cur]) return dp[l][cnt][pre][cur];
    vis[l][cnt][pre][cur] = true;
    int &ret = dp[l][cnt][pre][cur];
    int r;
    if(cur == 0) {
        r = n - l - cnt + 1;
    }else {
        r = n - l - cnt + pre + 1;
    }
    if(pre > r - l + 1) return ret = 0;

    if(cur == 0) {
        ret = sum[l + pre - 1] - sum[l - 1] + slove(l + pre, cnt, pre, 1);
        if(pre <= r - l) {
            ret = max(sum[l + pre] - sum[l - 1] + slove(l + pre + 1, cnt, pre + 1, 1), ret);
        }
    }else {
        ret = slove(l, cnt, pre, 0) - sum[r] + sum[r - pre];
        if(pre <= r - l) {
            ret = min(slove(l, cnt+1, pre+1, 0) - sum[r] + sum[r - pre - 1], ret);
        }
    }
    return ret;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        sum[i] = sum[i-1] + x;
    }
    memset(vis, false, sizeof(vis));
    printf("%d
", slove(1, 0, 1, 0));
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/cshg/p/6925048.html