[CF1101F]Trucks and Cities
题目
题解
对于这种求最小值的题,我们显然有一种二分的做法,先用 \(\log\) 的复杂度二分一个油箱的大小,再对其进行合法性检查,这样做时间复杂度 \(\mathcal O(nm\log 10^{18})\)
显然,这样做似乎要超时,虽然时间复杂度最差情况下就差一点就可以卡过去,但是这个方法好像无法进行优化了,如果要得到部分分,这个方法是很好的
考虑换个思路,对于一辆车,有 \(s_i,t_i,c_i,r_i\),其实就是将 \([s_i,t_i]\) 划分成 \(r_i+1\) 个区间,取最大的区间 \(\max l_i\) 再将其乘上 \(c_i\),然后对于 \(m\) 辆车取 \(\max\{c_i\times max l_i\}\)
现在问题是怎么得到 \(\max l_i\)?
考虑区间 \(DP\),定义 \(f[i][j][k]\) 为将 \([i,j]\) 划分成 \(k\) 个区间,使得这 \(k\) 个区间的最大值最小的值,显然有转移
状态 \(n^3\),转移 \(\mathcal O(n)\),那么总复杂度为 \(\mathcal O(n^4)\),对于 \(n\le 400\) 的数据,这有点困难
但是,注意到 \(\max\) 中的右边 \(a[j]-a[l]\),对于同一个 \(l\),当 \(j\) 变大时,\(a[j]-a[l]\) 也会变大,但是左边 \(f[i][l][k-1]\) 不改变,也就是说,在某一个 \(l\) 的位置使得 \(f[i][l-1][k-1]\ge a[j]-a[l-1]\) 且 \(f[i][l][k-1]<a[j]-a[l]\),当 \(j\) 变大之后,\(l\) 只会变大而不会变小,那么我们可以用一个指针一直指向这个位置,当 \(j\) 变大,指针也相应地向右移即可,这样均摊转移就是 \(\mathcal O(1)\) 的,总时间复杂度相应降为 \(\mathcal O(n^3)\).
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=400;
const int MAXM=250000;
const int INF=0x3f3f3f3f;
int a[MAXN+5];
int s[MAXM+5],t[MAXM+5],c[MAXM+5],r[MAXM+5];
vector<int>id[MAXN+5];
int n,m;
inline void Init(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<=m;++i){
scanf("%d %d %d %d",&s[i],&t[i],&c[i],&r[i]),++r[i];
r[i]=min(r[i],t[i]-s[i]);
id[r[i]].push_back(i);
}
}
int f[MAXN+5][MAXN+5][2],now=0;
inline void Getdp(){
for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)
f[i][j][now]=a[j]-a[i];
long long ans=-INF;
for(auto i:id[1])ans=max(ans,1ll*f[s[i]][t[i]][0]*c[i]);
for(int k=2;k<=n;++k){
now^=1;
for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)f[i][j][now]=INF;
for(int i=1;i<=n;++i)
for(int j=i,l=i;j<=n;++j){
while(f[i][l][now^1]<a[j]-a[l])++l;
f[i][j][now]=min(f[i][l][now^1],a[j]-a[l-1]);
}
for(auto i:id[k])ans=max(ans,1ll*f[s[i]][t[i]][now]*c[i]);
}
printf("%lld\n",ans);
}
signed main(){
Init();
Getdp();
return 0;
}
/*
10 10
2 3 4 8 9 10 12 13 15 19
3 8 3 1
3 4 3 2
1 9 2 1
1 9 3 1
6 10 2 1
3 9 2 0
3 7 2 1
2 3 3 0
3 9 2 0
4 10 3 0
output==21
ans==33
*/