joisc2017 D3T1 长途巴士 题解

joisc2017 D3T1 长途巴士 题解

这是学校 ACM 欢乐赛的题,赛时想到做法了,但是因为我各种唐诗没写出来

翻了

转化题面

我们发现,只可以踢掉检查站前面一个连续段的接水人,并且不能踢掉司机,考虑画出对整个路程表示的线段

上图几个小点是检查点,考虑在每个检查点之前踢掉一段的人,容易发现不能踢掉司机,后踢掉一个人一定不如在前面踢掉他优,所以区间没有交

考虑预处理出每个检查点踢掉每个人能节省的水费,直接 dp 即可,然而问题在于本题 \(10^{12}\) 的决策数

我们发现其实我们只关心踢掉哪些人和省了多少水,第二个已经预处理出来了,所以我们将人按照 \(D_i\) 排序,同样每次决策只会选择后面的一段,把 \(S_i \mod t\) 同时排序,发现其实贡献可以拆成两个部分,分别是

  1. 右端点(即检查点)的权值,也就是踢掉每个人的水费减少量
  2. 其他点的权值,也就是踢掉一个人要退的钱

暴力

我们容易写出转移方程

\[dp_i=\min_{1 \le k \le i}((k-pl_i-1)p_i+dp_{f(k)}+g(k,pl_i),dp_{i-1}) \]

稍微解释一下,\(f(x)\) 表示在 \(x\) 前面的第一个决策点,直接预处理。\(g(x,y)\) 表示将所有 $ x \le D \le y$ 的全部人赶下车要得到的钱,\(pl_i\) 是第 \(i\) 个决策点的位置,\(p_i\) 是能省的钱

发现可以预处理后缀和算 \(g(x,y)\),同时我们为了避免之后 dp 过程中用 $x \mod t $ 与 \(k\) 分讨,也把这个扔进去。

把一个人都不踢的钱和最后的 dp 值相加即为答案

复杂度 \(O(n^2)\)

优化

斜率优化板子

当做斜率优化笔记写吧

把式子展开,发现长这个样子(\(c\) 是后缀和)

\[dp_i=kp_i-pl_ip_i-p_i+dp_{f(k)}+c_k-c_{pl_i+1} \]

只有右边最前面那个是关于 \(i\)\(k\) 一次函数的乘积项,显然可以斜率优化

按照那个式子移个项

\[c_{pl_i+1}+pl_ip_i+p_i+dp_i-kp_i=dp_{f(k)}+c_k \]

固定 \(i\),左边那一坨都是定值,为了最小化 \(dp_i\),最小化那一整坨就完了,所以我们维护一个斜率单调递增的下凸壳,注意斜率是 \(p_i\),我们之前为了让 \(pl_i\) 有序,从而确定 dp 顺序,所以将决策排序了,\(p_i\) 被打乱了,并不单调,要在单调栈里面二分

代码

点击查看代码
#include<bits/stdc++.h>
#define llt long long
#define llb long double
using namespace std;
const llt N=200010; 
llt x,n,m,w,t,d[N],c[N],s[N],pl[N],p[N],f[N],dp[N],us,siz,sum,ans,tot,X[N],y[N],P,xx,yy,head=1,tail,last;
pair<llt,llt> pr[N],pi[N];
llb calc(llb xa,llb ya,llb xb,llb yb){return (yb-ya)/(xb-xa);}
llt find(llb K)
{
    llt l=head,r=tail,mid;
    while(l<r)
    {
        mid=l+r>>1;
        if(calc(X[mid],y[mid],X[mid+1],y[mid+1])<K)
            l=mid+1;
        else r=mid;
    }
    return l;
}
int main()
{
    #ifdef LOCAL 
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    scanf("%lld%lld%lld%lld%lld",&x,&n,&m,&w,&t); 
    for(int i=1;i<=n;i++)   scanf("%lld",&s[i]);
    for(int i=1;i<=m;i++)   
    {
        scanf("%lld%lld",&pr[i].first,&pr[i].second);
        if(x%t>=pr[i].first)    sum+=1+x/t;
        else sum+=x/t;
    }
    sum+=x/t+1;
    ans=sum*w;
    for(int i=1;i<=n;i++){pl[i]=s[i]%t,p[i]=x/t*w-(s[i]/t)*w;}
    pl[++n]=x%t;p[n]=0;sort(pr+1,pr+1+m);
    for(int i=1;i<=n;i++)   pl[i]=upper_bound(pr+1,pr+1+m,make_pair(pl[i],0ll))-pr-1;
    for(int i=1;i<=n;i++)   pi[i]=make_pair(pl[i],p[i]);
    sort(pi+1,pi+1+n);
    for(int i=1;i<=m;i++)   d[i]=pr[i].first,c[i]=pr[i].second;
    for(int i=1;i<=m;i++)   if(d[i]<=x%t)  c[i]-=w;
    for(int i=m;i>=1;i--)   c[i]=c[i+1]+c[i];
    for(int i=1;i<n;i++)
    {
        if(pi[i].first==pi[i+1].first)  continue;
        pl[++siz]=pi[i].first;
        p[siz]=pi[i].second;
    }
    pl[++siz]=pi[n].first;p[siz]=pi[n].second;
    for(int i=0;i<=siz;i++)
        for(int j=pl[i]+1;j<=pl[i+1];j++)
           f[j]=i;
    P=1;while(pl[P]==0)    P++;
    for(int i=1;i<=m;i++)
    {
        xx=i,yy=dp[f[i]]+c[i];
        while(head<tail&&calc(X[tail-1],y[tail-1],X[tail],y[tail])>=calc(X[tail],y[tail],xx,yy))    tail--;
        X[++tail]=xx,y[tail]=yy;
        if(i==pl[P]) {us=find(-p[P]);dp[P]=y[us]-c[pl[P]+1]-pl[P]*p[P]-p[P]+p[P]*X[us];dp[P]=min(dp[P],dp[P-1]);P++;}
    }
    cout<<dp[siz]+ans<<endl;
    return 0;
}
posted @ 2024-08-03 11:24  wang54321  阅读(42)  评论(4编辑  收藏  举报