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

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

【杂言】:

这题为什么直接复制题面呢,因为我想用这个图片,略略略!!

现在,有一根电线坏了。已知电线长度可能为 \(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的

\[f_{i,j}= min( max( f _{i, k}, f_{k + 1 , j}) + a_k ) \]

这个状态转移方程无需过多的解释了,推导其实也并不难推,我当时不知道怎么就想歪了。

复杂度是\(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 ;
}

有关的其他不明确的地方,吾将继续完善

posted @ 2021-01-07 21:47  SkyFairy  阅读(78)  评论(0编辑  收藏  举报