题目所要求的是欧气之和最大,则等价于欧气损失最小,而欧气损失当且仅当在连抽时会有损失,所以我们设一个
b
b
b数组,
b
[
i
]
=
∑
i
i
+
c
−
1
a
[
i
]
b[i]=\sum_{i}^{i+c-1}a[i]
b[i]=∑ii+c−1a[i],即代表如果连抽的开头选
i
i
i,那么损失就为
b
[
i
]
b[i]
b[i]
题目通过上述条件就转换为:选择
n
n
n 个
b
i
b_i
bi,它们两两的下标之差大于等于
c
c
c 且小于等于
c
+
d
c+d
c+d,求它们的和的最小值
就可以通过设
d
p
(
i
,
j
)
dp(i,j)
dp(i,j)为前i个选j个,且第
i
i
i次选了
b
i
b_i
bi的最小值,来求解
d
p
dp
dp方程为:
d
p
(
i
,
j
)
=
m
i
n
{
d
p
(
k
,
j
−
1
)
}
+
b
[
i
]
,
k
∈
[
i
−
c
−
d
,
i
−
c
]
dp(i,j)=min\{dp(k,j-1)\}+b[i],k\in [i-c-d,i-c]
dp(i,j)=min{dp(k,j−1)}+b[i],k∈[i−c−d,i−c],这个式子很明显就能利用单调队列来优化
d
p
dp
dp
AC代码(内含注释):
#include<bits/stdc++.h>usingnamespace std;constint maxn =2e5+10;typedeflonglong ll;int n, m, c, d, s, g, h, t;int a[maxn], q[maxn], pre[maxn][50];
ll sum[maxn], b[maxn];
ll dp[maxn][50];
vector <int> ans;intmain(){
cin >> n >> m >> c >> d;
s = c * n + m;
g = s - c +1;for(int i =1; i <= s; i++) cin >> a[i], sum[i]= sum[i -1]+ a[i];for(int i =1; i <= g; i++) b[i]= sum[i + c -1]- sum[i];memset(dp,20,sizeof(dp));
ll res = dp[0][0];for(int i =1; i <= d +1; i++) dp[i][1]= b[i];//因为不能连续单抽d次for(int j =2; j <= n; j++){
h =1, t =0;for(int i =1; i <= g; i++){//不属于范围内,则要弹出while(h <= t && q[h]< i - c - d) h++;//注意非常重要的一点,因为是[i-c-d,i-c]所以对于i来说,新增的区间右端点是i-c,即要入队的是i-c而不是iwhile(h <= t && i - c >0&& dp[i - c][j -1]< dp[q[t]][j -1]) t--;if(i - c >0) q[++t]= i - c;if(h <= t && i - c - d <= q[h]&& q[h]<= i - c) dp[i][j]= dp[q[h]][j -1]+ b[i], pre[i][j]= q[h];// q[++t] = i; //不应该这样压// int test_point = 0;}}int p =0;for(int i =1, j; i <= g; i++){
j = i + c -1;if(res > dp[i][n]&& s - j <= d)//之后的剩余数量不能超过d
res = dp[i][n], p = i;}// cout << sum[s] << endl;// cout << res << endl;
cout << sum[s]- res << endl;// for (int i = 1; i <= g; i++)// cout << pre[i] << " \n"[i == g];int temp = n;while(p) ans.push_back(p), p = pre[p][temp--];reverse(ans.begin(), ans.end());for(auto np : ans)
cout << np <<" ";
cout << endl;}/*
2 3 2 1
11 12 19 12 2 11 11
3 3 2 1
16 1 13 9 20 18 12 4 10
4 4 2 1
16 20 20 4 14 11 9 19 20 7 6 6
*/