1. 程式人生 > 實用技巧 >[CF1101F]Trucks and Cities

[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
*/