P6563 [SBCOI2020] 一直在你身旁 题解
【P6563 [SBCOI2020] 一直在你身旁】题解
【杂言】:
这题为什么直接复制题面呢,因为我想用这个图片,略略略!!
【题目】: link
现在,有一根电线坏了。已知电线长度可能为 \(1\),\(2\),...,\(n\) 中的一个数。现在,她需要知道电线的长度。
她可以花费 \(a_i\) 块钱购买长度为 \(i\) 的电线。购买这根电线后,她能知道所需要的电线长度是否 大于 \(i\)。
保证 \(a_1 \le a_2 \le \cdots \le a_n \le 10^9\)。
问她至少要花多少钱才能保证知道需要电线的长度。
本题捆绑测试,共有 \(4\) 个子任务。
\((Subtask 1)(10\%)\),\(n \le 15\)。
\((Subtask 2)(10\%)\),\(n \le 500\)。
\((Subtask 3)(30\%)\),\(n \le 2000\)。
\((Subtask 4)(50\%)\),没有任何额外限制。
对于100%的数据点, $ 1 \le n,\sum n \leq 7100,T \leq 500 $。
【题意】:
并没有什么题意,这个题很有意思吧,挺好玩的,笔者就是喜欢好玩的题,有美妙的背景的,有神奇的算法的题目啦,当然,若是还有什么好题,可以在评论区发一下并@我(应该不会有人这么做吧,毕竟没几个看我博文的,太菜了)
【思路分析】:
我们很容易就想到,如果使钱最小,那就是买的电线数最少,然后就很容易就想到二分,自然,很快,就会否定掉自己的想法 。
然后发现, 这道题由于它的询问是不断递进的,那么我们产生的\(DP\)值由于询问的递增而递增,所以我们可以用单调队列优化
【状态设计】:
先说一下吧 , 设\(f_{i,j}\)表示在区间\([i,j]\)的最小花费。
首先,由于我们每花费一次钱, 都会获得一个数值的界限,可能是上界,也可能是下界,但无论如何都是一个界限,同样的,我们在进行的计算花费的时候,我们需要的也就是这些界限,来判断是否是需要下一次的买电话线进行询问,所以,我们也会形成一个区间,这个区间的最小花费的状态也就不难想了,基本做过区间\(DP\)的人都了解了。
【状态转移】:
推状态转移用了20min了,我太菜了
类比区间\(DP\)的常规解法,我们只需要枚举区间的右端点或者枚举区间的长度,同时我们枚举在这个区间内,任意的取一个值,看其是否满足题意,并看一下这个的花费,由于\(a_k\)是不一样的,这也是用\(DP\)的缘由了,不然我觉得是不需要用DP的
这个状态转移方程无需过多的解释了,推导其实也并不难推,我当时不知道怎么就想歪了。
复杂度是\(O(n^3)\)的,期望得分: 20pots
【优化】:
我们发现,对于题目中的捆绑测试中,给了500,那么这时候\(n^2logn\)显然是可以过掉的
考虑对这个状态转移方程进行魔改 , 我们可以先忽略掉其中\(min\),我们看到其中的\(max\)那么很显然,因为我们的\(a_i\)是递增的,那么我们的询问值也是单调递增的,那么我们就可以二分一下我们的询问值, 从而获得这个最大值 , 复杂度\(O(n^2logn)\),期望得分,可以拿到pots : 50 了
再考虑别的优化:
我们发现,如果没有其中的这个\(max\),那么也就是\(f_{i,j} = \min\limits_{l = i} ^{l=j}num + a_l\) ,因为我们的询问值是有序的,然后就是单调队列,好像还是十分的朴素。但是其中的\(max\)还没有解决,怎么办呢?
考虑解决\(max\)的问题(到了这,我就挂掉了,借鉴了一下其他题解的处理),
若\(f_{i,k} > f_{k+1, j}\)时 ,那么很显然,我们在进行决策的时候,会选择\(f_{i,k}\),同理,若\(f_{i,k} < f_{k+1 , j}\)时,那么很显然 ,我们在进行决策的时候,会选择\(f_{k+1,j}\) .
那么根据这个推理,我们发现,其中会有一个中转站,也就是存在一个节点\(g\)导致决策改变,我们的决策是\(O(1)\),的,我们只需要看一下\(k\)在\(g\)的哪一侧,就可以直接判断是哪一个是最大值。
一个很显然的推论:
在节点\(i\)固定时, \(f_{i,j}\)单调不降(后面的电线的钱都比前面的大 ),当节点\(j\)确定时 , \(f_{i,j}\)单调不增 (同理)。
【代码实现】:
\(struct \ \ 1\) : 先求解转折点\(g\) ,
\(struct \ \ 2\) : 当为上方第一种情况时 , 我们根据推论,直接取 最左端的即可
\(struct \ \ 3\) : 当为第二种情况时, 直接枚举就好了
【Code】:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <cstring>
#define int long long
using namespace std ;
const int base = 13331 ;
const int kmaxn = 10000 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , head , tail ;
int num[kmaxn] ;
int f[kmaxn][kmaxn] ;
pair<int,int > q[kmaxn] ;
void pop(int now)
{
while(head < tail && q[head].second >= now) ++head ;
}
void insert(pair<int,int> now)
{
while(head < tail && q[tail - 1].first >= now.first )
tail --;
q[tail++] = now ;
}
int front()
{
if(head < tail) return q[head].first ;
else return (int)2147483648 ;
}
void slove()
{
for(int r = 1 ; r <= n ; r++)//枚举区间长度
{
head = tail = 0 ;
for(int l = r - 1, k = r - 1 ; l >= 0 ; l-- )
{
//struct 1
while(k >= l && f[l][k] >= f[k + 1][r]) k--;
//struct 2
pop(k + 1) ;
// struct 3
f[l][r] = min(f[l][k+1] + num[k+1] , front()) ;
if(l > 0)
{
insert(pair<int,int>(f[l][r] + num[l - 1] , l - 1)) ;
}
}
}
}
signed main()
{
int t = read() ;
while(t--)
{
n = read() ;
for(int i = 1 ; i <= n ; i++)
{
num[i] = read() ; //题目中已经说过了 a 是递增的
}
slove() ;
printf("%llu\n" , f[1][n]) ;
}
return 0 ;
}
有关的其他不明确的地方,吾将继续完善