Loading

题解 【P4852 yyf hates choukapai】

\(\Large\texttt{P4852}\)

联赛前的一篇 \(\texttt{DP}\) 题解,祝各位不要挂分。

标签:单调队列优化 \(\texttt{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\) 密切有关的 \(\texttt{DP}\)

先建立 \(\texttt{dp[i]}\) ,表示前 \(i\) 个数的最大价值,如果普通暴力转移,时间会爆炸并且这个方程有后效性的(即前i个数末尾有几个单选的数可能会影响到后面的选择)。

考虑二维 \(\texttt{DP}\) ,建立\(\texttt{dp[i][j]}\) 表示前 \(i\) 个数有 \(j\) 个长为 \(c\) 的子序列,很容易得到转移方程,每一步都 \(j-1\) 转移上来。

可以列出状态转移方程:

\[\texttt{dp[i][j]=dp[k-c][j-1]+sum[i]-sum[k]+a[k-c+1]} \]

其中,方案为在 \(k-c+1\)\(k\) 选一段长为 \(c\) 序列,在 \(k+1\)\(i\)\(i-k\) 个单个的数。

\(k\) 的范围十分显然,\(i-d \le k \le i\)

但是一算复杂度直接炸了,考虑优化。


观察式子,发现没有带 \(k\) 的项与带 \(i\)的项相关联,可以把sum[i]单独提出来,剩下的全和 \(k\) 有关。

单调队列可做。

每次将式子入单调队列,并排除 \(k\) 过小的情况可以做到 \(\texttt{O(1)}\) 转移。

方案的计算就再开一维记录每一个状态从何而来。

总复杂度 \(\large\texttt{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\n",dp[mx][a][0]);
    print(dp[mx][a][1],a-1);
    return 0;
}
posted @ 2020-11-05 10:05  RedreamMer  阅读(68)  评论(0编辑  收藏  举报