题解 P6563 【[SBCOI2020] 一直在你身旁】
可能是既视感太强,我做这道时总是被二分查找的思想干扰X
解析
首先要想到一个暴力的区间 dp:
设
接下来我们要设法将这个方程优化成
首先可以从定义得知
所以对于一对
并且若固定
于是我们就可以先枚举一个端点,再枚举一个端点,并在第二层枚举内设一个指针保存临界点位置。可以知道该指针的移动是单调的,因此这部分总的复杂度还是两层枚举,
现在我们考虑确定了
假设我们先枚举
注意到题目中的
(单调队列部分具体可见代码,相关变量的定义都是一样的)
若不满足 a_1\leq a_2\leq\cdots\leq a_n
其实即使题目不保证
我们先倒序地枚举
我们再对每个端点维护一个维护最小值的单调队列,设当前刚刚枚举到
若当前枚举到的
最后,再将
注意到这里的
我们对每个
(也可以先正序地枚举
但这种方法在过程中需要维护 我还没试过,不知道会不会爆qaq。试过了,的确会炸,而且大数组寻址、维护多一倍的单调队列带来的常数也不小(
CODE
满足
#include <cstdio>
#include <iostream>
#define ll long long
using std::pair;
using std::min;
typedef pair<ll, int> pad;
const int MAXN =7110;
/*------------------------------Monotone queue------------------------------*/
pad q[MAXN];
int head, tail;
inline ll front(){
if(head < tail)
return q[head].first;
else
return 0x3f3f3f3f3f3f3f3f;
}
void modifyfront(int nowtime){
while(head < tail && q[head].second >= nowtime)
++head;
}
void push(pad newelement){
while(head < tail && q[tail-1].first >= newelement.first)
--tail;
q[tail++] =newelement;
}
/*------------------------------Main------------------------------*/
inline int read(){
int x =0; char c =getchar();
while(c < '0' || c > '9') c =getchar();
while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (48^c), c =getchar();
return x;
}
ll dp[MAXN][MAXN];
int a[MAXN];
int main(){
for(int t =0, T =read(); t < T; ++t){
int n =read();
for(int i =0; i < n; ++i)
a[i] =read();
for(int r =1; r < n; ++r){
head =tail =0;
/*mid: 转移方程分界点*/
for(int l =r-1, mid =r-1; l >= 0; --l){
while(mid >= l && dp[l][mid] >= dp[mid+1][r])
--mid;
modifyfront(mid+1);
dp[l][r] =min(dp[l][mid+1]+a[mid+1], front());
if(l > 0)/*避免最后越界*/
push(pad(dp[l][r]+a[l-1], l-1));
}
}
printf("%lld\n", dp[0][n-1]);
}
}
不满足
/*不要求 ai 单调不减的版本*/
/*注意会炸空间*/
#include <cstdio>
#include <iostream>
#define ll long long
using std::pair;
using std::min;
typedef pair<ll, int> pad;
const int MAXN =7101;
/*------------------------------Monotone queue------------------------------*/
pad q1[MAXN], q2[MAXN][MAXN];
int head1, tail1, head2[MAXN], tail2[MAXN];
inline ll front(const int &head, const int &tail, const pad *q){
if(head < tail)
return q[head].first;
else
return 0x3f3f3f3f3f3f3f3f;
}
void modifyfront1(const int &nowtime){
while(head1 < tail1 && q1[head1].second <= nowtime)
++head1;
}
void modifyfront2(const int &nowtime, int &head, const int &tail, const pad *q){
while(head < tail && q[head].second >= nowtime)
++head;
}
void push(const pad &newelement, const int &head, int &tail, pad *q){
while(head < tail && q[tail-1].first >= newelement.first)
--tail;
q[tail++] =newelement;
}
/*------------------------------Main------------------------------*/
inline int read(){
int x =0; char c =getchar();
while(c < '0' || c > '9') c =getchar();
while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (48^c), c =getchar();
return x;
}
ll dp[MAXN][MAXN];
int a[MAXN];
int main(){
for(int t =0, T =read(); t < T; ++t){
int n =read();
for(int i =0; i < n; ++i)
a[i] =read(), head2[i] =tail2[i] =0;
for(int l =n-2; l >= 0; --l){
head1 =tail1 =0;
/*由于实现上跳过了 l == r 的部分,所以这里要对单调队列初始化*/
/*初始化: [l+1, l+1] ( 这其实是在替左端点为 l+1 ( 上一次的 l ) 时的 l == r 的时刻初始化 )*/
push(pad(0+a[l], l), head2[l+1], tail2[l+1], q2[l+1]);
/*初始化: [l, l]*/
push(pad(0+a[l], l), head1, tail1, q1);
/*mid: 转移方程分界点*/
/*这里的 mid 和题解略有不同,这里 [mid, r-1] 均取 dp[l][k],[l, mid-1] 均取 dp[k+1][r]*/
for(int r =l+1, mid =l; r < n; ++r){
while(mid < r && dp[l][mid] <= dp[mid+1][r])
++mid;
modifyfront1(mid-1);
modifyfront2(mid, head2[r], tail2[r], q2[r]);
dp[l][r] =min(front(head1, tail1, q1), front(head2[r], tail2[r], q2[r]));
push(pad(dp[l][r]+a[r], r), head1, tail1, q1);
if(l > 0)/*避免最后越界*/
push(pad(dp[l][r]+a[l-1], l-1), head2[r], tail2[r], q2[r]);
}
}
printf("%lld\n", dp[0][n-1]);
}
}