正题
题目链接:https://www.luogu.com.cn/problem/P6563
题目大意
长度为nnn的序列aia_iai,现在有一个随机[1,n][1,n][1,n]的整数,每次你可以花费aia_iai询问这个数字是否大于iii,求猜出所有数至少要多少花费。
T≤500,∑n≤7000T\leq 500,\sum n\leq 7000T≤500,∑n≤7000
保证aia_iai单调不降
解题思路
考虑区间dpdpdp,设fl,rf_{l,r}fl,r表示猜出区间[l,r][l,r][l,r]的最小花费。
最基本的转移就是
fl,r=min{max{fl,k,fk+1,r}+ak}(k∈[l,r))f_{l,r}=min\{\ max\{f_{l,k},f_{k+1,r}\}+a_k\ \}(\ k\in[l,r)\ )fl,r=min{ max{fl,k,fk+1,r}+ak }( k∈[l,r) )
然后考虑如何优化转移。
因为里面有个maxmaxmax,我们可以对于一个l,rl,rl,r考虑找到一个最小的zzz满足fl,z>fz+1,rf_{l,z}>f_{z+1,r}fl,z>fz+1,r那么zzz以后的都是用fl,zf_{l,z}fl,z,以前的都是用fz+1,rf_{z+1,r}fz+1,r。
这个在右端点固定左端点向左时zzz是不升的,所以不用二分带logloglog。
对于取fl,k+akf_{l,k}+a_kfl,k+ak的那一部分,aka_kak和fl,zf_{l,z}fl,z都随着kkk增大不降,所以直接取fl,z+azf_{l,z}+a_zfl,z+az。
对于fk+1,r+akf_{k+1,r}+a_kfk+1,r+ak的那一部分,kkk的限制会不断缩小,所以用一个单调队列维护就可以了。
时间复杂度O(∑n2)O(\sum n^2)O(∑n2)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=7200;
int T,n,a[N];
long long f[N][N];
deque<int> q;
long long calc(int k,int r){if(k<1)return 1e18;return f[r][k+1]+a[k];
}
signed main()
{scanf("%d",&T);while(T--){scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=j)f[i][j]=1e18; for(int r=2;r<=n;r++){q.clear();q.push_back(r-1);for(int l=r-1,z=r-1;l>=1;l--){while(z>l&&f[z-1][l]>f[r][z])z--;while(!q.empty()&&q.front()>=z)q.pop_front();if(!q.empty())f[r][l]=calc(q.front(),r);f[r][l]=min(f[r][l],f[z][l]+a[z]);if(l==1)continue;while(!q.empty()&&calc(q.back(),r)>=calc(l-1,r))q.pop_back();q.push_back(l-1);}}printf("%lld\n",f[n][1]);}
}