CodeFestival16FinalH
题意
\(A\) 和 \(B\) 玩游戏。有 \(n\) 个数,从左到右 \(1\) 到 \(n\),每个数有权值 \(a_i\)。\(A\) 在第 \(1\) 个数上,\(B\) 在第 \(2\) 个数上,每次左边的那个人选择右边一个没有被人占过的位置过去,若无这样的位置游戏结束。两人的得分为所占的所有格子的权值和。
\(q\) 次询问,每次询问给 \(x\),问 \(a_n\ =\ x\) 时 \(A\) 得分 \(-\ B\) 得分最大值是多少。(两人均最优策略)
\(1\ \leq\ n\ \leq\ 200000,\ 0\ \leq\ a_i\ \leq\ 10^6,\ \sum_{i\ =\ 1}^{n\ -\ 1}\ a_i\ \leq\ 10^6,\ q\ \leq\ 200000,\ 0\ \leq\ x\ \leq\ 10^9\)。
做法1
令 \(dp_i\) 表示 \([i...n]\) 游戏先手 \(-\) 后手得分的最大值,可以发现 \(dp_i\ =\ |a_i\ -\ dp_{i+1}|\)。
令 \(f_{i,x}\) 表示当 \(dp_{i+1}\ =\ x\) 时,\(dp_1\) 的值。则 \(f_{i,x}\ =\ f_{i-1,|a_i-x|}\),即将 \(f_{i-1}\) 向右平移 \(a_i\) 位,再以 \(a_i\) 为轴翻转。用 \(deque\) 维护即可。
每次询问找到后缀和 \(>\ x\) 的最大的 \(i\),答案就是 \(f_{i,x-\sum_{j>i}a_j}\)。
时间复杂度 \(O(n\ +\ q\ +\ \sum_{i\ =\ 1}^{n\ -\ 1}\ a_i)\)。
代码
#include <bits/stdc++.h>
#ifdef __WIN32
#define LLFORMAT "I64"
#else
#define LLFORMAT "ll"
#endif
using namespace std;
int main() {
ios::sync_with_stdio(false);
int n, s = 0, base = 0;
cin >> n;
--n;
int foo, bar;
cin >> foo >> bar;
base = foo - bar;
n -= 2;
vector<int> a(n), suf_sum(n + 1, 0);
vector<vector<pair<int, int> > > all(n);
for (int i = 0; i < n; ++i) cin >> a[i], s += a[i];
vector<int> rem(s);
for (int t = 0, i = n - 1; ~i; --i) {
suf_sum[i] = suf_sum[i + 1] + a[i];
for (int j = 0; j < a[i]; ++j) rem[t + j] = i;
t += a[i];
}
int m;
cin >> m;
vector<int> q(m), ans(m);
for (int i = 0; i < m; ++i) {
cin >> q[i];
if(q[i] >= s) ans[i] = q[i] - s + base;
else all[rem[q[i]]].push_back(make_pair(i, q[i] - suf_sum[rem[q[i]] + 1]));
}
deque<int> dp;
for (int i = 0; i < n; ++i) {
while(dp.size() <= a[i]) dp.push_back(dp.size() == 0 ? 0 : dp.back() + 1);
for (int j = dp.size() - 1, end_j = j - a[i]; j > end_j; --j) dp.push_front(dp[dp.size() - j]);
for (auto t: all[i]) ans[t.first] = dp[t.second] + base;
}
copy(ans.begin(), ans.end(), ostream_iterator<int>(cout, "\n"));
return 0;
}