• [AGC011F] Train Service Planning [线段树优化dp+思维]


    思路

    模意义

    这题真tm有意思
    我上下楼梯了半天做出来的qwq

    首先,考虑到每K分钟有一辆车,那么可以把所有的操作都放到模$K$意义下进行

    这时,我们只需要考虑两边的两辆车就好了。

    定义一些称呼:

    上行:从$0$~$n$的车

    下行:从$n$~$0$的车

    定义数组:

    $p[i](i=0...n)$表示上行车在站台$i$的停车时长

    $q[i](i=0...n)$表示下行车在站台$i$的停车时长

    $a[i](i=0...n-1)$表示第$i$的站台与第$i+1$个站台中间的行驶时间(也就是题目给定的数组)

    我们再定义$S(a,i)$表示数组$a$从零开始到$i$的前缀和函数,即:

    $S(a,i)=sum_{j=0}^i a[j]$

    区间不交

    考虑两辆车合法的情况。显然,他们俩在每一段铁路上行驶的时间是一个区间。双向铁路不用管,一定合法,我们发现如果单向铁路上合法,那么上下行的车在某一段单向铁路上行驶的区间肯定不相交

    我们把上下行车辆在铁路$k$上的行驶时间区间写出来:

    上行:$(S(a,i-1)+S(p,i),S(a,i)+S(p,i))$

    下行:$(-S(a,i-1)-S(q,i),-S(a,i)-S(q,i))$

    注意这里的下行部分是一个很巧妙的转化:因为我们在模意义下运算,所以下行部分本来应该是用两个后缀和加在一起的,但是这样上下行式子不统一

    所以,我们把后缀和看做总和减掉前缀和,那么总和可以调整为$K$的倍数,就没掉了,所以就是负的前缀和

    这两段区间如果不交,那么显然任意一个端点不在另外一个区间里面

    这样我们可以得到一批不等式,大概类似于这个形式:

    $-2S(a,i-1)>S(p,i)+S(q,i)>-S(a,i-1)-S(a,i)$

    最终化一下,可以得到这个结论:

    $S(p,i)+S(q,i) otin (-2ast S(a,i),-2ast S(a,i-1))$

    可以看到对于每个$i$,不可选的区间是固定的

    那么我们考虑在模$K$的意义下,右边这个区间的补集,我们设它为$[L[i],R[i]]$

    $S(p,i)+S(q,i) in [L[i],R[i]]$

    问题变化

    我们发现,本题的答案,实际上就是最小化的$S(p,n)+S(q,n)+2ast S(a,n)$,等价于最小化$S(p,n)+S(q,n)$

    这样,我们可以把问题转化为:

    给定$n$个区间,任选起点,走$n$步,第$i$步需要落在区间$i$中,求最小总路径长度

    注意这里的“走”实际上,从一个大的走向小的,是要走到$K$,然后从$0$再出来(因为我们是模$K$意义下的)

    这样就可以变成一个$DP$问题了

    线段树优化$DP$

    我们现在考虑新的这个问题,显然可以使用$dp$来做

    首先有一个贪心结论:如果起点已经确定了,那么每次需要走的时候,走到下一个区间的左端点肯定最优,证明显然

    那么我们可以先预处理出来在每个起点出发后,一直走左端点直到走完,我需要的最小距离

    预处理

    定义$dp[i]$表示对于区间$[L[i],R[i]]$的左端点$L[i]$而言,一直走左端点到$n$的最短路径

    那么我们倒着从$n$到$1$推这个$dp$

    每推完一个$dp[i]$,我们就把区间$[L[i],R[i]]$的补集在线段树上的值,覆盖为$i$

    然后推$dp[i]$时,每次查询$L[i]$位置的线段树上的值,设这个值是$j$

    那么显然,编号在区间$[i,j-1]$中的所有区间都覆盖了$L[i]$这个点

    所以直接在这里不动,就可以走完这些区间了

    那么我们用$dp[j]+dis(L[i],L[j])$来更新$dp[i]$

    然后再覆盖区间,就完成了$dp$预处理过程

    统计答案!

    最后一步,我们枚举所有出现的$L[i]$和$R[i]$,并把它们作为起点,求出答案。

    显然如果不选这些端点的话,答案只会更劣

    统计出的答案,就是原来问题中的$S(q,n)+$S(p,n)$,也就是我们要最小化的东西

    加上$2S(a,n)$输出即可

    做完啦~

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cassert>
    #define ll long long
    using namespace std;
    inline ll read(){
        ll re=0,flag=1;char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-') flag=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
        return re*flag;
    }
    ll n,K,cnt,a[200010],b[200010],tmp[500010],seg[500010],L[200010],R[200010],dp[200010],sum[200010];
    void push(ll num){
        if(!seg[num]) return;
        seg[num<<1]=seg[num<<1|1]=seg[num];
        seg[num]=0;
    }
    void change(ll l,ll r,ll ql,ll qr,ll num,ll val){
        if(ql>qr) return;
        if(l>=ql&&r<=qr){seg[num]=val;return;}
        push(num);
        ll mid=(l+r)>>1;
        if(mid>=ql) change(l,mid,ql,qr,num<<1,val);
        if(mid<qr) change(mid+1,r,ql,qr,num<<1|1,val);
    }
    ll query(ll l,ll r,ll num,ll pos){
        if(l==r) return seg[num];
        push(num);
        ll mid=(l+r)>>1;
        if(mid>=pos) return query(l,mid,num<<1,pos);
        else return query(mid+1,r,num<<1|1,pos);
    }
    ll ask(ll pos){
        ll choose=query(1,cnt,1,pos);
        if(!choose) return 0;
        return (dp[choose]+(tmp[L[choose]]-tmp[pos]+K)%K);
    }
    ll erfen(ll val){
        ll l=1,r=cnt,mid;
        while(l<r){
            mid=(l+r)>>1;
            if(tmp[mid]>=val) r=mid;
            else l=mid+1;
        }
        assert(val==tmp[l]);
        return l;
    }
    int main(){
        n=read();K=read();ll i;
        for(i=1;i<=n;i++) {
            a[i]=read();b[i]=read();
            sum[i]=(sum[i-1]+a[i]);
            if(b[i]==2) continue;
            if(2*a[i]>K){
                puts("-1");return 0;
            }
        }
        for(i=n;i>=1;i--){
            if(b[i]==1){
                L[i]=(-2ll*sum[i-1]%K+K)%K;
                R[i]=(-2ll*sum[i]%K+K)%K;
            }
            else L[i]=0,R[i]=K-1;
            tmp[++cnt]=L[i],tmp[++cnt]=R[i];
        }
        sort(tmp+1,tmp+cnt+1);
        cnt=unique(tmp+1,tmp+cnt+1)-tmp-1;
        for(i=n;i>=1;i--){
            L[i]=erfen(L[i]);
            R[i]=erfen(R[i]);
            dp[i]=ask(L[i]);
            if(L[i]>R[i]) change(1,cnt,R[i]+1,L[i]-1,1,i);
            else change(1,cnt,1,L[i]-1,1,i),change(1,cnt,R[i]+1,cnt,1,i);
        }
        ll ans=dp[1];
        for(i=cnt;i>=1;i--) ans=min(ans,ask(i));
        printf("%lld
    ",ans+sum[n]*2ll);
    }
    
  • 相关阅读:
    SQL之CASE WHEN用法详解
    MySQL笔记汇总
    Linux常用命令
    TCP/IP速记
    数据结构和算法速记
    多线程相关概念
    线程安全&Java内存模型
    线程通讯wait&notify
    创建多线程的4种方式
    重写ThreadPoolTaskExecutor
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/9646722.html
Copyright © 2020-2023  润新知