[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\) 个区间的最大值最小的值,显然有转移

\[f[i][j][k]=\min\{\max\{f[i][l][k-1],a[j]-a[l]|l\in [i,j]\}\} \]

状态 \(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
*/

猜你喜欢

转载自www.cnblogs.com/Arextre/p/13398959.html