noip模拟赛 补兵_编程题

分析:比较难想的一道dp题.要想补兵的数量最多,最后每个小兵的血量肯定是呈一个阶梯状的:i,i+1,i+2......i+k.那么记录一下每个血量i离它最近的小兵的血量是多少,记作cur[i].那么把这个小兵砍成i就需要砍cur[i] - i次,显然一轮是不可能有这么多次的,只有前面的血量不去砍,留到i才有可能有砍这么多次.

      那么状态就设计为f[i][j]表示砍到了血量为i的小兵,还剩j次刀能继续砍的最大补兵数.这么设计状态的思路就是如果只用一个f[i]来记录状态的话,不知道能否达到这个状态,可能我把cur[i]砍到i之后,i-1就已经被砍死了,所以我要看前一个i-1能留多少刀给i去砍,对于i,可以将cur[i]的小兵砍成i,也可以不砍,如果不砍,那么f[i][j] = f[i-1][j-1],因为没砍,所以多了一次操作次数.如果砍的话,f[i][j] = max{f[i][j],f[i-1][j + cur[i] - i] + 1},cur[i]这个兵的血量可以变成i了,那么这个兵就能被补了,之前留的刀数就是j+cur[i]-i.不过j是有范围限制的,j<=i,因为血量i的小兵最多i次就会被老鹿砍死,最多只有i次机会,同样的,j+cur[i]-i <= i-1,因为是i-1留下的次数嘛.大致的原理就是将所有小兵的血量尽可能地变成需要的阶梯状,每次留下来的次数就留给下一次砍.

      挺难想到的.还是有一点贪心的思想,先要想出什么样的局面是最优的,再用dp来求怎么要到这样的局面.

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

using namespace std;

int T, n, a[1010], ans, f[1010][1010], maxn,sta[1010], cur[1010], top, cnt[1010];

int main()
{
    scanf("%d", &T);
    while (T--)
    {
        memset(sta, 0, sizeof(sta));
        memset(f, 0, sizeof(f));
        top = 0;
        ans = 0;
        memset(cur, 0, sizeof(cur));
        memset(cnt, 0, sizeof(cnt));
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            cnt[a[i]]++;
            maxn = max(maxn, a[i]);
        }
        for (int i = 1; i <= maxn; i++)
        {
            if (cnt[i] == 0)
                sta[++top] = i;
            else
            {
                while (cnt[i] > 1 && top > 0)
                {
                    cur[sta[top--]] = i;
                    cnt[i]--;
                }
                cur[i] = i;
            }
        }
        for (int i = 1; i <= maxn; i++)
            for (int j = 0; j <= i; j++)
            {
                if (j > 0)
                    f[i][j] = f[i - 1][j - 1];
                if (cur[i] != 0 && j + cur[i] - i <= i - 1)
                    f[i][j] = max(f[i][j], f[i - 1][j + cur[i] - i] + 1);
                ans = max(ans, f[i][j]);
            }
        printf("%d\n", ans);
    }

    return 0;
}