题目分析:
2500的题目为什么我想了这么久。。。
考虑答案是什么。对于一辆从$s$到$t$的车,它有$k$次加油的机会。可以发现实际上是将$s$到$t$的路径以城市为端点最多划分为最大长度最小的$k+1$段。不难发现这样做一定是最优的。
设计DP状态$f[i][j][k]$表示将第$i$座城市到第$j$座城市的路径划分为$k$段。朴素的DP方程是:
$$f[i][j][k] = min(max(f[i][x][k-1],a[j]-a[x])),(which)x in [i,j]$$
这个DP是$O(n^4)$的,但我们注意到DP状态有单调性。
换句话说,存在一个城市$y$,使得任意$x<y$满足$max(f[i][x][k-1],a[j]-a[x])=a[j]-a[x]$,任意$x>y$满足$max(f[i][x][k-1],a[j]-a[x])=f[i][x][k-1]$。我们考虑的转移只有两个点是有意义的。
对于相同的$i$,$y$的选取仍然具有单调性,所以利用two pointers移动指针可以做到$O(n^3)$
注意:开三维数组会MLE,我们可以滚动一维(甚至可以滚动两维,心动不如行动,试着写一下吧)。
代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 405; 5 6 int n,m,a[maxn],nxt[maxn]; 7 8 struct node{int s,t,oil;}; 9 10 int f[maxn][maxn][2]; 11 vector <node> hh[maxn]; 12 13 void read(){ 14 scanf("%d%d",&n,&m); 15 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 16 for(int i=1;i<=m;i++){ 17 int s,t,oil,tms; 18 scanf("%d%d%d%d",&s,&t,&oil,&tms); 19 tms = min(tms,t-s-1)+1; 20 hh[tms].push_back((node){s,t,oil}); 21 } 22 } 23 24 void work(){ 25 long long ans = 0; 26 for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) f[i][j][1] = a[j]-a[i]; 27 for(int k=1;k<=n-1;k++){ 28 for(int i=0;i<hh[k].size();i++) 29 ans = max(ans,1ll*f[hh[k][i].s][hh[k][i].t][k&1]*hh[k][i].oil); 30 for(int i=1;i<=n;i++){ 31 f[i][i][(k+1)&1] = 0; int pts = i; 32 for(int j=i+1;j<=n;j++){ 33 while(f[i][pts][k&1] < a[j]-a[pts])pts++; 34 f[i][j][(k+1)&1] = min(f[i][pts][k&1],a[j]-a[pts-1]); 35 } 36 } 37 } 38 printf("%lld ",ans); 39 } 40 41 int main(){ 42 read(); 43 work(); 44 return 0; 45 }