联赛前的一篇 ( exttt{DP}) 题解,祝各位不要挂分。
标签:单调队列优化 ( exttt{DP}) 。
这也是我第一次写单调队列题解,写的不好请见谅QwQ。
并输出选择方案。
题意
一段序列,你要在序列里选择 (n) 段长 (c) 的子序列,价值为序列第一个数,还要选 (m) 个数,价值为它本身,求最大的价值和,但是不能选连续 (d) 个数,求最大价值和。
(1 le n le 40),(1 le d le m le 80000),(2 le c le 3000)
保证有解。
思路
看上去不这么显然观察到 (n) 只有 (40) ,可能是一个与 (n) 密切有关的 ( exttt{DP})。
先建立 ( exttt{dp[i]}) ,表示前 (i) 个数的最大价值,如果普通暴力转移,时间会爆炸并且这个方程有后效性的(即前i个数末尾有几个单选的数可能会影响到后面的选择)。
考虑二维 ( exttt{DP}) ,建立( exttt{dp[i][j]}) 表示前 (i) 个数有 (j) 个长为 (c) 的子序列,很容易得到转移方程,每一步都 (j-1) 转移上来。
可以列出状态转移方程:
其中,方案为在 (k-c+1) 到 (k) 选一段长为 (c) 序列,在 (k+1) 到 (i) 选 (i-k) 个单个的数。
(k) 的范围十分显然,(i-d le k le i)
但是一算复杂度直接炸了,考虑优化。
观察式子,发现没有带 (k) 的项与带 (i)的项相关联,可以把sum[i]单独提出来,剩下的全和 (k) 有关。
单调队列可做。
每次将式子入单调队列,并排除 (k) 过小的情况可以做到 ( exttt{O(1)}) 转移。
方案的计算就再开一维记录每一个状态从何而来。
总复杂度 (large exttt{O(n(nc+m))})
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6;
const int M = 40;
inline int read()
{
register int s = 0;
register bool neg = 0;
register char c = getchar();
for (; c < '0' || c > '9'; c = getchar())
neg |= (c == '-');
for (; c >= '0' && c <= '9'; s = s * 10 + (c ^ 48), c = getchar())
;
return (neg ? -s : s);
}
int a,b,c,d,mx,dp[N+10][M+10][2],s[N+10],sum[N+10],qu[N+10][2],l,r;
inline int calc(int n,int m) {
return dp[n-c][m][0]-sum[n]+s[n-c+1];
}
inline void print(int n,int m) {
if(m==-1) return;
print(dp[n][m][1],m-1);
printf("%lld ",n+1);
}
signed main()
{
// freopen("in1.in", "r", stdin);
a=read();
b=read();
c=read();
d=read();
mx=a*c+b;
for(int i=1;i<=mx;i++) s[i]=read(),sum[i]=s[i]+sum[i-1];
for(int i=1;i<=d;i++) dp[i][0][0]=sum[i];
for(int i=1;i<=a;i++) {
l=1,r=0;
for(int j=i*c;j<=mx;j++) {
while(l<=r&&qu[r][0]<calc(j,i-1)) r--;
qu[++r][0]=calc(j,i-1);
qu[r][1]=j;
while(l<=r&&qu[l][1]<j-d) l++;
dp[j][i][0]=qu[l][0]+sum[j];
dp[j][i][1]=qu[l][1]-c;
}
}
printf("%lld
",dp[mx][a][0]);
print(dp[mx][a][1],a-1);
return 0;
}