1. 程式人生 > >noip模擬賽 補兵

noip模擬賽 補兵

ans 才有 space clas 分析 一次 iostream () ems

技術分享

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

noip模擬賽 補兵