题解P6563 [SBCOI2020] 一直在你身旁

P6563 [SBCOI2020] 一直在你身旁

一道非常值得做的单调队列优化DP,刚开始看到这道题目一直想着二分,愣是没有看出是个DP,后来仔细想了下,可以轻易的写出 \(O(n^3)\) 的暴力.

定义 \(f_{i,j}\) 表示确定区间 \([i,j]\) 内的数字所需的最小花费,得出转移方程

\[f_{i,j}=\min(\max_{l\le k < r}(f_{l,k},f_{k+1,r})+a_k) \]

显然是个区间DP

通过标签观察式子有点类似 \(f_k+a_k\) 的形式,似乎可以优化,首先不好直接对这个式子下手,可以分类讨论一下,把其中的 \(\max\) 去掉

仔细思考一下,可以发现 \(f_{i,j}\le f_{i,j+1}, f_{i,j} \ge f_{i+1,j}\)

那么也就是说,我们在分类讨论的时候只需要找到一个最小的 \(x\) 使得 \(f_{l,x}>f_{x+1,r}\) 就行了

对于 \(f_{l,k}>f_{k+1,r}\) 的情况

\(f_{l,r}=\min\limits_{l\le k < r} f_{l,k}+a_k\),其中 \(f_{l,k}+a_k\) 是单调不减的,所以答案直接为 \(f_{l,x}+a_x\)

对于 \(f_{l,k}<f_{k+1,r}\) 的情况就稍微比较麻烦了

\(f_{l,r}=\min\limits_{l\le k < r}(f_{k+1,r}+a_k)\),每次用单调队列维护一个最小值就行了.

所以时间复杂度就可以优化成 \(O(n^2)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 7105;
typedef long long ll;

ll f[N][N];
int a[N], q[N];

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int n;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for (int i = 1; i <= n; i++)
            f[i][i] = 0, f[i][i + 1] = a[i];
        for (int r = 2; r <= n; r++)
        {
            int front = 0, tail = -1, p = r - 1;
			q[0] = r - 1;
			for (int l = r - 2; l >= 1; l--)
            {
				while (p >= l && f[l][p] > f[p + 1][r])//找到最小的x
                    p--;
                f[l][r] = f[l][p + 1] + a[p + 1];//这里主要需要加一
				if (front <= tail && q[front] > p)//第二种情况
                    front++;
                if (front <= tail)  
                    f[l][r] = min(f[l][r], f[q[front] + 1][r] + a[q[front]]);
                while (front <= tail && f[q[tail] + 1][r] + a[q[tail]] >= f[l + 1][r] + a[l])
                    tail--;
                q[++tail] = l;
			}
        }
        printf("%lld\n", f[1][n]);
    }
    return 0;
}
posted @ 2021-04-13 11:07  DSHUAIB  阅读(61)  评论(0编辑  收藏  举报