分析:比较难想的一道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; }