【洛谷P6563】一直在你身旁 题目 思路 代码

题目链接:https://www.luogu.com.cn/problem/P6563
回到这座小镇后,她的新工作是维修电线。
现在,有一根电线坏了。已知电线长度可能为 (1)(2),...,(n) 中的一个数。现在,她需要知道电线的长度。
她可以花费 (a_i) 块钱购买长度为 (i) 的电线。购买这根电线后,她能知道所需要的电线长度是否 大于 (i)
保证 (a_1 le a_2 le cdots le a_n le 10^9)
问她至少要花多少钱才能保证知道需要电线的长度。
(Qleq 500,sum nleq 7100)

思路

容易想到设 (f[i][j]) 表示电线长度在区间 ([i,j]) 时,至少需要多少钱才能知道电线的长度。
有转移

[f[i][j]=min_{kin [i,j)}(max(f[i][k],f[k+1][j])+a_k) ]

时间复杂度 (O(n^3))
我们发现转移中有一个 (max) 并不好处理,所以考虑分类把 (max) 拆开。
对于一个区间 ([i,j]),我们设 (k) 表示满足 (f[i][k]>f[k+1][j]) 的最小的位置。不难当 (i) 不变时 (k) 是单调不减的。所以可以在枚举区间的时候顺便求出。
考虑转移点的位置 (l)

  • 如果 (lgeq k),也就意味着转移式为 (min(f[i][l]+a_l)),显然 (l) 越大时价格越大,所以直接取 (l=k) 即可。
  • 如果 (l<k),那么转移式为 (min(f[l+1][j]+a_l)),这个东西虽然不单调,但是我们现在等价于求在 ([i,k)) 区间内的一个最小值,且 (k) 是不断减小的。所以我们可以先枚举 (j) 再枚举 (i),然后单调队列维护一下即可。

时间复杂度 (O(n^2))

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=7110;
int Q,n,a[N];
ll f[N][N];
deque<int> q;

int main()
{
	scanf("%d",&Q);
	while (Q--)
	{
		scanf("%d",&n);
		for (int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		for (int j=1;j<=n;j++)
		{
			while (q.size()) q.pop_back();
			for (int i=j-1,k=j-1;i>=1;i--)
			{
				while (q.size() && f[i+1][j]+a[i]<=f[q.back()+1][j]+a[q.back()]) q.pop_back();
				q.push_back(i);
				while (k>i && f[i][k-1]>f[k][j]) k--;
				f[i][j]=f[i][k]+a[k];
				while (q.size() && q.front()>=k) q.pop_front();
				if (q.size()) f[i][j]=min(f[i][j],f[q.front()+1][j]+a[q.front()]);
			}
		}
		printf("%lld
",f[1][n]);
	}
	return 0;
}