回顾单调队列主要思想
单调队列可以维护当前点前面一段区间里的答案来进行转移,通过保持单调性来保留最优且最新的答案,把旧的答案直接跳过。
模板
1 2 3 4 5 6 7 8 9 10
| for (int i = 1; i <= n + 1; i++) { if (i - Q.front() > k) Q.pop_front(); dp[i] = dp[Q.front()] + a[i]; while (!Q.empty() && dp[Q.back()] >= dp[i]) Q.pop_back(); if (s[i] == '1') { Q.clear(); } Q.push_back(i); }
|
B.Ropeway
观察题目建模
题意解释:可以看作一个数轴上有 $n$ 个点,选取一些点连接,这些点要满足距离不大于 $k$,且这些点将首末相连接,每个点使用的价格不同,接下去有 $q$ 次临时修改一个点的价格,再求其对应的最小价格。
观察与单调队列联系
通过朴素的 $dp$ 可以发现得到当前点的答案需要从前面 $k$ 个点转移,因此可以单调队列维护,此时跑一遍后可以得到没有修改过的时候的每个点的答案。然后观察修改这个操作,一个点前后转移顶多只会转移到 $k$ 个以内,因此修改了一个点影响的决策只会包括这个点后面 $k$ 个的决策,$qk$ 的复杂度从题目要求来看也能够接受,因此考虑单点修改了之后对后面 $k$ 个点之外的答案的影响,然后加上后面到当前点的最优后缀的决策,也就是说预处理的时候要处理一个前向的和一个后向的,然后重新处理到 $i$ 这个点的时候加上后缀的答案就行了。
有一点比较重要就是重新处理的时候不能直接开始重新处理,要考虑这个点前面 $k$ 个点的决策,也会对后面的有影响因为可能跳过了这个点,所以要先正常跑前 $k$ 个点跑出单调队列的状态,然后再开始维护新的答案跑后 $k$ 个点。
具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| while (q--) { int u, v; cin >> u >> v; if (s[u] == '1') { cout << dp[n + 1] + v - a[u] << endl; continue; } int ans = 1e18; int tmp = a[u]; a[u] = v; Q.clear(); for (int i = max(0ll, u - k); i <= u - 1; i++) { while (!Q.empty() && i - Q.front() > k) Q.pop_front(); while (!Q.empty() && dp[Q.back()] > dp[i]) Q.pop_back(); if (s[i] == '1') { Q.clear(); } Q.push_back(i); } for (int i = u; i <= min(n + 1, u + k); i++) { while (!Q.empty() && i - Q.front() > k) Q.pop_front(); dp[i] = dp[Q.front()] + a[i]; while (!Q.empty() && dp[Q.back()] > dp[i]) Q.pop_back(); if (s[i] == '1') { Q.clear(); } Q.push_back(i); } for (int i = u; i <= min(n + 1, u + k); i++) { ans = min(ans, dp[i] + sufdp[i]); dp[i] = dpre[i]; } a[u] = tmp; cout << ans << endl; }
|