题解 P6563 【[SBCOI2020] 一直在你身旁】
题目链接
这道题题解写的太简洁了,这个 蒟蒻 看的很费劲,于是他决定写一篇更详细一点的题解。
基础分析:
基础 DP 方程:
这个式子还是很难理解的(也可能是我太菜了)。
首先是
显然当区间
此时,我们就可以逆推,从确定的元区间
但朴素做法是
优化:
看了一眼暴力的 DP 的式子,感觉有点单调队列优化的感觉,可以试一下:
但是这个方程
先考虑 l,r 已固定的情况。
仔细观察,在
然后转换一下即是:当
于是我们总算有一点眉目了,即对每一个区间
接下来继续考虑 l,r 移动的情况。
我们移动区间
肯定的是它的位置是单调的,也即对于任意区间
列一下表就可以得到:
-
-
\large x_{l,r}\in[l+k_1,r+k_1] - 若
\large x_{l+k_1,r+k_1} \in (l+k_1,r] ,这种情况下,一定有\large x_{l+k_1,r+k_1} == x_{l,r} 。 - 若
\large x_{l+k_1,r+k_1}\in(r,r+k_1] ,显然\large x_{l+k_1,r+k_1} > x_{l,r} 一定成立。
- 若
我自己都不是很明白为什么要把那么显然的一个东西分类讨论,可能因为我是个蒟蒻。
综上,我们可以确定中转点是具有单调性。
进一步分析,当中转点确定时,dp[l][r] 可能的取值。
很明显,它是需要分类讨论。
-
dp[l][k] > dp[k+1][r]\ (1) -
dp[l][r] = \min\limits_{l \leq k < r}(dp[l][k]+a[k])
-
这一个式子根据上面
-
dp[l][k] \leq dp[k+1][r]\ (2) -
dp[l][r] = \min\limits_{l\leq k < r}(dp[k+1][r]+a[k])
-
这一个式子是满足单调性的,可以直接普通单调队列维护。
综上,我们成功将时间复杂度优化到了
代码实现:
下面贴的代码省略了前面漫长的手写 IO ,转移方面借鉴了 JohnVictor 大佬的提供的优化,将
# define N 7100
# define reg register
class ysys_Deque
{
private:
int d_e_q_u_e[(N<<1) + 5],Rear_,Front_;
public :
ysys_Deque(){Rear_=N;Front_=N+1;}
~ysys_Deque(){}
inline bool Empty(){return Front_ > Rear_;}
inline int Size(){return Front_ <= Rear_ ? Rear_-Front_+1 : 0;}
inline void Reset(){Rear_=N;Front_=N+1;}
inline int Front(){return d_e_q_u_e[Front_];}
inline int Rear(){return d_e_q_u_e[Rear_];}
inline int Get_Back(){return d_e_q_u_e[Rear_--];}
inline int Get_Front(){return d_e_q_u_e[Front_++];}
inline void Pop_Back(){--Rear_;}
inline void Pop_Front(){++Front_;}
inline void Push_Front(const int Val){d_e_q_u_e[--Front_] = Val;}
inline void Push_Back(const int Val){d_e_q_u_e[++Rear_] = Val;}
} q;//手写双端队列
using namespace ysys::MMS;
long long f[N+5][N+5],a[N + 5];
int n,T;
using namespace ysys::unsigned_IO_f;
int main()
{
T = Read();
while(T--)
{
n = Read();
for(reg int i = 1; i <= n ; ++i) a[i] = ReadL();
for(reg int r = 2; r <= n ; ++r)
{//如果枚举区间长度,,改为枚举右端点。
q.Reset();q.Push_Back(r-1);
f[r][r-1] = a[r-1];
for(reg int l = r-2,k = r; l ; --l)//倒序枚举便于对右区间选取。
{
while(f[k-1][l] > f[r][k] && l < k) --k;//寻找中转点。
f[r][l] = f[k][l] + a[k];//取dp[l][k]的转移。
while(q.Size() && k <= q.Front()) q.Pop_Front();//将小于了中转点 k 的dp[k+1][r] 出队。
if(q.Size()) f[r][l] = Min(f[r][l],f[r][q.Front()+1] + a[q.Front()]);
while(q.Size() && f[r][q.Rear()+1] + a[q.Rear()] >= f[r][l+1] + a[l]) q.Pop_Back();
//新增加的可能成为右区间最优解的 k+1。
q.Push_Back(l);
}
}
printf("%lld\n",f[n][1]);
}
return 0;
}
总结
个人感觉这道题的单队优化还是很难想的,能独立想出来的,单队也可以说是学的差不多了(蒟蒻个人见解勿喷)。