Logo S08577 的博客

博客

摆渡车(常规+斜率优化)

...
S08577
2025-12-01 12:57:32

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-08 17:16:22

题意

有 $n$ 个人在等车,第 $i$ 个人从 $t_i$ 时刻开始等车。摆渡车来回一趟需要 $m$ 分钟。求所有人最小等车时间。

思路

该题显然用动态规划。

不妨设 $f_i$ 为从开始到第 $i$ 时刻每个人最小等车时间。

不难发现:

$$f_i=\min_{j \le i-m}(f_j+\sum_{j < t_k \le i} i-t_k)$$

但是,这个方程中的 $\sum_{j < t_k \le i} i-t_k$ 很难处理。我们不妨将其拆开:

$$\sum_{j < t_k \le i} i-t_k \\ =\sum_{j < t_k \le i} i -\sum_{j < t_k \le i} t_k$$

看见求和,我们不难想到使用前缀和。不妨设 $cnt_i$ 为从开始到 $i$ 时刻有多少人等车,$sum_i$ 为从开始到 $i$ 时刻的位置之和。

不难发现:

$$\sum_{j < t_k \le i} i-t_k \\ =\sum_{j < t_k \le i} i -\sum_{j < t_k \le i} t_k \\ = i \times (cnt_i-cnt_j)-(sum_i-sum_j)$$

所以,状态转移方程即为:

$$f_i = \min_{j \le i-m}(i \times (cnt_i-cnt_j)-(sum_i-sum_j)) $$

最后,由于最后一个人的到达时间为 $t$,我们只需要查询 $\min_{1 \le i \le t+k} f_i$

但是,时间复杂度为 $O(t^2)$ 只能拿到$50$ $pts$。

不妨进行优化,不难发现,当时间小于 $i-2m$ 时,乘客肯定已经坐上车了,所以无需考虑 $i-2m$ 之前的情况。

可将式子优化为:

$$f_i = \min_{i-2m \le j \le i-m}(i \times (cnt_i-cnt_j)-(sum_i-sum_j)) $$

可是,复杂度仍然爆炸,只能去的 $75$ $pts$。

我们不难发现,摆渡车以 $m$ 为一个周期。如果从 $i-m$ 到 $i$ 没有人上车,则这段区间为无用区间,令 $f_i=f_{i-m}$。

这样,我们就可以AC了。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define ENdl Endl
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

const int N=4e6+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;

int n,m;
int a[N];
int f[N],cnt[N],sum[N];


\/*
f_i=min j<=i-m(f_j+sigma i-t_k)
 *\/
signed main(){
    
\/\/   freopen("a.in","r",stdin);
\/\/   freopen("a.out","w",stdout);
    cin>>n>>m;
    int t=0;
    for(int i=1;i<=n;i++) {
        int x;
        cin>>x;
        cnt[x]++;
        sum[x]+=x;
        t=max(t,x);
    }
    for(int i=1;i<t+m;i++){
        cnt[i]+=cnt[i-1];
        sum[i]+=sum[i-1];
    }
    for(int i=0;i<t+m;i++){
        if(i>=m&&cnt[i]==cnt[i-m]){
            f[i]=f[i-m];
            continue;
        }
        f[i]=cnt[i]*i-sum[i];
        for(int j=max(i-m-m+1,0ll);j<=i-m;j++){
            f[i]=min(f[i],f[j]+i*(cnt[i]-cnt[j])-(sum[i]-sum[j]));
        }
        
    }
    int res=Max;
    for(int i=t;i<t+m;i++){
        res=min(res,f[i]);
    }
    cout<<res;

    return 0;
}



\/*
 1
 5 3
 2 4 5

 *\/

斜率优化版

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define ENdl Endl
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

const int N=4e6+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;

int n,m;
int a[N];
int f[N],cnt[N],sum[N],q[N];

double get_k(int x,int y){
    return double(f[y]+sum[y]-f[x]-sum[x])\/(cnt[x]==cnt[y]?1e-9:  cnt[y]-cnt[x]);
}
\/*
f_i=min j<=i-m(f_j+sigma i-t_k)
 *\/
signed main(){
    
\/\/   freopen("a.in","r",stdin);
\/\/   freopen("a.out","w",stdout);
    cin>>n>>m;
    int t=0;
    for(int i=1;i<=n;i++) {
        int x;
        cin>>x;
        cnt[x]++;
        sum[x]+=x;
        t=max(t,x);
    }
    for(int i=1;i<t+m;i++){
        cnt[i]+=cnt[i-1];
        sum[i]+=sum[i-1];
    }
    int l=1,r=0;
    for(int i=0;i<t+m;i++){
        if(i-m>=0){
            while(l<r&&get_k(q[r-1],q[r])>=get_k(q[r],i-m)){
                r--;
            }
            q[++r]=i-m;
        }
        while(l<r&&get_k(q[l],q[l+1])<=i) l++;
        f[i]=i*cnt[i]-sum[i];
        if(l<=r){
            f[i]=min(f[i],f[q[l]]+i*(cnt[i]-cnt[q[l]])-(sum[i]-sum[q[l]]));
        }
    }
    int res=Max;
    for(int i=t;i<t+m;i++){
        res=min(res,f[i]);
    }
    cout<<res;

    return 0;
}



\/*
 1
 5 3
 2 4 5

 *\/

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。