http://acm.hdu.edu.cn/showproblem.php?pid=3851
题意:有一只怪兽,在白天攻击一次能损失它pd点血,晚上攻击则损失它pn点血,每次攻击的间隔为t,每天的白天时间与晚上时间分别为t1i, t2i,问N天内最多攻击它多少血。n <= 1000, t <= 100
思路:dp,先把问题等价为n×2段,每段的价值为相应的pd或者为pn,不妨都记为pi,显然每次记录一个时间段最后t时间的攻击状况即可,f[i][j]表示第i个时间段最后一次攻击在第tt[i]-j的最优值,则转移方程为
f[i][j] = max{ g[p[i][j]] + ((tt[i] - j - 1)/t+1) * pi), h[p[i][j]+1][k] + ((tt[i] - j - 1) / t + 1), g[i - 1] + ((tt[i] - j - 1) / t) };
其中,g[i]为前i段所有情况中最优的结果,p[i][j]=max{p`| tt[p` + 1] + tt[p` + 2] + ... + tt[i - 1] + (tt[i] - j - 1) % t >= t }, k满足k + tt[p + 2] + tt[p + 3] + ... + tt[i - 1] + (tt[i] - j - 1) % t = t,h[p][k] = max{ f[p][k], f[p][k + 1], ... , f[p][t - 1]}
这个方程的意思是找以下情况中最大的
(1) 前p段最大的,然后满足tt[p + 1] + tt[p + 2] + ... + tt[i - 1] + (tt[i] - j - 1) % t >= t, 所以在第i段从任意点开始攻击,最多攻击((tt[i] - j - 1)/t+1)次;
(2) 在第p+1段,最后, 最后一次攻击在第k次,因为 k满足k + tt[p + 2] + tt[p + 3] + ... + tt[i - 1] + (tt[i] - j - 1) % t = t, 所以同理在第i段从任意点开始攻击,最多攻击((tt[i] - j - 1)/t+1)次;
(3) 在第i段放少一个,那么必然可以使得第一段末尾至少可以空出t-1的时间点出来,也就肯定不会跟第i-1的时间段的攻击产生冲突。
列好方程之后首先预处理出p和k,
因为如果固定最后攻击的时间,固定次数(最大可能情况), 那么肯定尽可能放在后面攻击,所以如果对于这样的一个固定的区间,最迟第一次攻击可以在区间(tt[i] - j - 1) % t 后,而且,显然(tt[i] - j1 - 1) % t > (tt[i] - j2 - 1) % 2, p[i][j1] > p[i][j2], 所以先在线性时间内求出所以t2[r], 则p[i][j] = t2[(tt[i] - j - 1) % t ];
k = t - (tt[p + 2] + tt[p + 3] + ... + tt[i - 1] + (tt[i] - j - 1) % t)。
此时转移时间在预处理之后能在O(1)时间内完成,最后时间复杂度为O(nt).
其实若t = 1, pn = pd = 2 ^ 31 - 1, tt[i] = 2 ^ 31. 那么答案应该会超出long long的范围,不过我尝试不写高精度过了。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define sqr(x) ((x) * (x))
#define two(x) (1 << (x))
#define Rep(i, s, n) for (int i = s; i < (n); ++i)
#define X first
#define Y second
typedef long long LL;
const int MAXN = 2012;
const double eps = 1e-9;
const int INF = 1000000000;
int n, t, pn, pd, tt[MAXN];
LL f[MAXN][200], g[MAXN], ss[MAXN];
int pre[MAXN][200], tmp[200];
void init()
{
scanf("%d%d%d%d", &n, &t, &pd, &pn);
Rep(i, 0, 2 * n) scanf("%d", &tt[i + 1]);
ss[0] = 0;
Rep(i, 0, 2 * n) ss[i + 1] = ss[i] + tt[i + 1];
Rep(i, 1, 2 * n + 1)
{
tmp[0] = 0;
Rep(j, 0, i) if (ss[i - 1] - ss[i - j - 1] >= t)
{
tmp[0] = i - j - 1;
break;
}
Rep(j, 1, min(t, tt[i]))
{
tmp[j] = tmp[j - 1];
while (ss[i - 1] - ss[tmp[j] + 1] + j >= t) ++tmp[j];
}
int rem = tt[i] % t;
Rep(j, 0, min(t, tt[i]))
{
rem = (rem + t - 1) % t;
pre[i][j] = tmp[rem];
}
}
}
void work()
{
g[0] = 0;
Rep(i, 1, n * 2 + 1)
{
LL pt = ((i & 1)? pd: pn);
Rep(j, 0, min(t, tt[i]))
{
f[i][j] = g[pre[i][j]] + pt * ((tt[i] - j - 1) / t + 1);
int rem = (tt[i] - j - 1) % t;
if (rem + ss[i - 1] >= t)
f[i][j] = max(f[i][j], f[pre[i][j] + 1][t - 1 - rem] + pt * ((tt[i] - j - 1) / t + 1));
if (tt[i] - j - 1 >= t)
f[i][j] = max(f[i][j], g[i - 1] + pt * ((tt[i] - j - 1) / t));
}
Rep(j, 1, min(t, tt[i]))
{
f[i][min(t, tt[i]) - j - 1] = max(f[i][min(t, tt[i]) - j - 1], f[i][min(t, tt[i]) - j]);
}
g[i] = max(g[i - 1], f[i][0]);
}
cout << g[n * 2] << endl;
}
int main()
{
int T, ca = 0;
scanf("%d", &T);
while (T--)
{
printf("Case %d: ", ++ca);
init();
work();
}
return 0;
}
然后看着今天上海的board,觉得自己没去好幸福……