Problem
给出一个从0开始,长度为n+1一段序列,从1到n每个点上都有一定价值的金币a[i](可正可负)。从0号结点开始,每次可以跳跃不超过p个结点,跳到结点i上就(假设从i号结点跳到j号结点, i < j , j − i ≤ p i < j, j - i\leq p i<j,j−i≤p )。有q个询问,每次给出一个x( 1 ≤ x ≤ n 1 \leq x \leq n 1≤x≤n),问每次只能跳到x倍数的结点上(如果该结点编号小于n的话,只能跳到x的整数倍结点上。当跳到小于等于n的最后一个x的整数倍节点上时,只要最后一步跳出n,且这一步不超过p即可),同时,每次跳跃不能超过p,且最终必须跳到大于n的结点上:问多可以收集多少价值的金币。如果不能跳到大于n的结点上,则输出"Noob"。
Solution
- 状态表示:dp[i]表示跳到结点i上能够收集的最大价值的金币。
- 状态转移:
d
p
[
i
]
=
m
a
x
(
d
p
[
j
]
)
+
a
[
i
]
(
i
%
x
=
0
,
j
%
x
=
0
,
i
−
j
≤
p
)
dp[i] = max(dp[j]) + a[i]~~~~~~(i \% x = 0, j \% x = 0,i - j \leq p)
dp[i]=max(dp[j])+a[i] (i%x=0,j%x=0,i−j≤p)
可以用单调队列维护可以转移到状态i的几种状态。 - 注意:最终必须跳到大于n的节点上,最后一个状态应该是
n + 1
,而不是最后一个x的整数倍节点上(n / x * x + x
)。而且这两个状态不等价。
例如: n = 10,p = 5, x = 2.
当最后一种状态是n + 1
时(11),可以由 dp[6],dp[8],dp[10]中最大的一个转移过来。
而,最后一种状态是n / x * x + x
时(12),可以由dp[8],dp[10]中最大的一个转移过来。
显然,两种情况不等价,尤其需要注意。
Code
const int N = 2e6 + 6;
ll dp[N];
ll a[N];
unordered_map<ll, ll>mp;
int main()
{
IOS;
int n, q, p; cin >> n >> q >> p;
for (int i = 1; i <= n; i++)
cin >> a[i];
while (q--)
{
deque<int> q;
int x; cin >> x;
if (mp.count(x))
{
cout << mp[x] << endl;
continue;
}
if (x > p) cout << "Noob\n";
else
{
dp[0] = 0;
q.push_back(0);
vector<int>b;
b.push_back(0);
for (int i = x; i <= n; i += x)
b.push_back(i);
b.push_back(n + 1);//注意:本题中n以内只能跳x的倍数,n以外可以不是x的倍数,所以不能写成n / x * x + x
for (int i = 1; i < sz(b); i++)
{
while (q.empty() == 0 && b[i] - b[q.front()] > p) q.pop_front();
dp[i] = dp[q.front()] + a[b[i]];
while (q.empty() == 0 && dp[q.back()] <= dp[i]) q.pop_back();
q.push_back(i);
}
mp[x] = dp[sz(b) - 1];
cout << mp[x] << endl;
}
}
return 0;
}
Tips
注意:要好好读题,这题最后一个状态尤其细节。。也至关重要