codeforces 1101F Trucks and Cities 区间dp+单调优化 好题

题目传送门

题意简述:(来自洛谷)

n个城市坐落在一条数轴上,第ii个城市位于位置ai

城市之间有m辆卡车穿行.每辆卡车有四个参数:si为起点编号,fi为终点编号,ci表示每行驶1个单位长度需要消耗的油量,ri表示可以在路途中加油的次数.

当卡车到达一个城市的时候可以将油加满(当然也可以不加),在路中无法加油,但是路途中总加油次数不能超过ri

所有卡车的油箱都是一样大的,我们称它的容积为V.试求一个最小的V,使得对于所有的卡车都存在一种方案,在路途中任意时刻油箱内的油量大于等于0且路途中总加油次数小于等于ri的情况下从起点城市到达终点城市.

n,m(n400,m250000)表示城市数量与卡车数量。

思路:

  此题学习了洛谷的博客,但洛谷的博客有地方是错误的,导致自闭了许久,自己证明了一波,才走出自闭。

  洛谷题解 点这里   但是洛谷题解有错,并且最重要的单调性没有证明。

  首先,主体是一个区间DP

  设 dp{i,j,k} 为:从第 i 个城市到第 j 个城市分成 k 段,这 k 段中长度最大的一段的最小值

  边界: dp{i,j,0}=aj-ai(1≤i≤j≤n)。

  状态转移方程

  dp {i,j,k}=dp{i,j,k}=min (max(dp{i,w,k1},ajaw))(0<kn))( i <= w <= j )

  目标:max(ci*dp{si,fi,ri​})  i<=m

      以上均取自洛谷,并且洛谷的状态转移方程还写错了。上面这个区间dp的时间复杂度是O(n4)的,显然会超时,要进行优化,洛谷题解中说单调性是显然得出的,,然而我证明了好久。

      先说两个结果:

  1)当 k,i 确定, j 在不断向右移时,对每个 j 取到的 w 具有单调性。

  2)同时,当 j 确定时,不同的 w 对应的取值呈“先减后增” 的趋势,

  我们先证明第二点,当i j k 确定时,转移方程中  我们设dp{i,w,k1}​为 Aw,ajaw​ 为Bw,当w变大时,Aw可能变大,Bw必定变小,所以取值一开始肯定是取Bw的,慢慢变的有可能取Aw,可以想象,这个dp方程一开始肯定是w越大越好,但是当某一个临界点,如果比前面大了,那我们会发现,此时的最大值必定不是Bw,因为Bw<B(w-1),这是必定的。所以最大值是Aw,而w越大,Aw则可能变大,但绝不变小,所以不会有变小的趋势了,证毕。

  然后证明第一点,假设j'=j+1,我们先看w是否会前移。发现j变成j'后,只有Bw会变大,Aw是不变的,而越往后的项,最大值取Aw的可能性越大,所以前面的项只会变大,就算当前项变大了,那变大的程度也会和前面的一样,所以取最小值的话当前w必定由于小于w的值。

  那么看w是否会后移,由于Bw会变大,Aw只是可能变大,后面的项比前面的项更有可能用到Aw,所以后面的项可能更优,w可能后移,有单调性,证毕。

  注意不要开数组不要long long,会爆内存,也不要开太大,开了410*410*410会mle。

#include<bits/stdc++.h>
#define clr(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll; 
const int maxn=402;
ll ans;
int dp[maxn][maxn][maxn],a[maxn];
int n,m;
int main(){
    while(cin>>n>>m)
    {
        ans=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i;j<=n;j++)
            {
                dp[i][j][0]=a[j]-a[i];
            }
        }
        for(int k=1;k<=n;k++)
        {
            for(int i=1;i<=n;i++)
            {
                int w=i;
                for(int j=i;j<=n;j++)
                {
                    while(w<j&&max(dp[i][w][k-1],a[j]-a[w])>max(dp[i][w+1][k-1],a[j]-a[w+1]))w++;
                    dp[i][j][k]=max(dp[i][w][k-1],a[j]-a[w]);
                }
            }
        }
        int s,f,r;
        ll c;
        while(m--)
        {
            scanf("%d%d%lld%d",&s,&f,&c,&r);
            ans=max(ans,dp[s][f][r]*c);
        }
        cout<<ans<<endl;
    }
} 
View Code

 

posted @ 2019-02-03 14:56  光芒万丈小太阳  阅读(238)  评论(0编辑  收藏  举报