【题解】P11303 [NOISG 2021 Finals] Pond

P11303 [NOISG 2021 Finals] Pond

题意

\(n\) 个路灯,路灯 \(i\) 与路灯 \(i+1\) 距离为 \(d_i\),每秒没有被关闭的路灯都会消耗 \(1\) 电能。

你初始时在 \(m\),每秒可以移动 \(1\) 单位距离,在移动过程中关闭所经过的路灯,直到所有路灯被关闭。

最小化消耗的总电能。

\(n\le 3\times 10^5\)

题解

知识点:动态规划,斜率优化,李超线段树。

启发:

  • 贡献提前计算。

  • 代价分开计算。

不妨对 \(d_i\) 做前缀和,得到 \(a_i\),表示 \(i\) 的位置,\(a_1=0\)

首先有一个不需要怎么动脑子的区间 dp,设 \(dp_{l,r,0/1}\) 为关闭了 \(l\sim r\) 的路灯,最后停在左/右端点的最小消耗。

初始条件:\(dp_{m,m,0}=dp_{m,m,1}=0\)

答案:\(\min (dp_{1,n,0},dp_{1,n,1})\)

有如下转移:

\[\begin{cases} dp_{l,r,0}=\min(dp_{l+1,r,0}+(a_{l+1}-a_{l})(l+n-r),dp_{l+1,r,1}+(a_{r}-a_{l})(l+n-r))\\ dp_{l,r,1}=\min(dp_{l,r-1,0}+(a_{r}-a_{l})(l+n-r),dp_{l,r-1,1}+(a_{r}-a_{r-1})(l+n-r)) \end{cases} \]

上面用到了贡献提前计算的思想,因为对于每个路灯,很难刻画他什么时候被关闭,所以使用一种巧妙的方法来变相维护。

以第一个转移第一个式子为例,从 \(l+1\) 走到 \(l\),耗时 \(a_{l+1}-a_l\),这个过程中,\(1\sim l\)\(r+1\sim n\) 的路灯都在消耗电能,所以加上 \((l+n-r)(a_{l+1}-a_l)\) 的消耗。

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

代码如下,结合 \(d_i=1\) 的特判,可以获得 \(24\) 分。

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()

#define N 300010
#define int long long

int n,m,a[N],dp[2025][2025][2];

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    cin>>n>>m;

    bool one=1;

    rep(i,2,n){
        int x;
        cin>>x;
        a[i]=a[i-1]+x;

        one&=(x==1);
    }

    if(one){
        int ans1=0,ans2=0;

        rep(i,1,m){
            ans1+=(a[m]-a[i]);
        }
        rep(i,m+1,n){
            ans1+=(a[m]-a[1])*2+a[i]-a[m];
        }

        rep(i,m,n){
            ans2+=(a[i]-a[m]);
        }
        rep(i,1,m-1){
            ans2+=(a[n]-a[m])*2+a[m]-a[i];
        }

        cout<<min(ans1,ans2);

        return 0;
    }

    rep(i,1,n){
        dp[i][i][0]=dp[i][i][1]=3e18;
    }

    dp[m][m][0]=dp[m][m][1]=0;

    rep(len,2,n){
        rep(l,1,n){
            int r=l+len-1;
            if(r>n){
                break;
            }

            dp[l][r][0]=min({dp[l+1][r][0]+(a[l+1]-a[l])*(l+n-r),dp[l+1][r][1]+(a[r]-a[l])*(l+n-r)});
            dp[l][r][1]=min({dp[l][r-1][0]+(a[r]-a[l])*(l+n-r),dp[l][r-1][1]+(a[r]-a[r-1])*(l+n-r)});
        }
    }

    cout<<min(dp[1][n][0],dp[1][n][1]);

    return 0;
}

但是 \(n\le 3\times 10^5\),区间 dp 的状态包含两个端点,状态数十分爆炸,能否拆开两个端点,分别考虑?

通过上面的观察,可以发现答案由两部分组成,每个路灯到第 \(m\) 个路灯的距离之和加上额外的电能消耗,前者是固定的,所以考虑最小化后者。

先思考最优的移动路径长什么样子,肯定是从 \(m\) 出发,走到 \(m\) 的一边,再掉头走到 \(m\) 的另一边,不会出现类似在 \(m\) 的右边,往左走但没走到 \(m\),又转头往右走的情况,这种是纯浪费。

可以发现,每一次的掉头走一段距离,都会关闭新的路灯,也都会跨过 \(m\)

那就找到了分界点 \(m\),接着将两个端点分开,定义 \(f_i(i\le m)\) 表示走到 \(i\) 位置的最小消耗,\(g_i(i\ge m)\) 表示走到 \(i\) 位置的最小消耗。

根据上面的推理,\(f_i\) 一定是从 \(g_i\) 转移过来(跨过 \(m\)),反之亦然,可以得出以下转移:

\(\displaystyle f_i=\min_{j\ge m} g_j+2(a_j-a_i)(n-j)\)

\(\displaystyle g_i=\min_{j\le m} f_u+2(a_i-a_j)(j-1)\)

依旧是运用了贡献提前计算的思想,以第一个式子为例,\(f_i\)\(g_j\) 转移过来,相当于从 \(j\) 走到 \(i\),虽然距离为 \(a_j-a_i\),但是还是会走回去的(或者从 \(g_n\) 转移过来,但是此时没有额外贡献),且一定会再次经过 \(j\),此时路程为 \(2(a_j-a_i)\),相比于正常走,第 \(j+1\sim n\) 个路灯晚到了 \(2(a_j-a_i)\),所以提前计算他们的贡献,显然 \(1\sim i-1\) 的贡献不用计算。

发现无法确定转移的顺序,此时需要的是一个类 dijkstra 状物式的拓展,设定 \(l=r=m\),表示当前 \(f_l\)\(g_r\),每次 \(f_l\) 选择当前最优的 \(g_i(i\in[m,r])\) 往左拓展,同样地每次 \(g_r\) 选择当前最优的 \(f_i(i\in[l,m])\) 往右拓展,当两者都能拓展时,贪心地选择 dp 值最小的拓展,这基于的是 \(g_i,f_i\) 都是单调的,根据状态的定义就能轻易证明。

最终可以得到一个 \(O(nk)\) 的做法。

再转过头看转移式,发现可以斜率优化,所以直接上李超线段树,复杂度 \(O(n \log n)\)

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define bg(x) (x).begin()
#define ed(x) (x).end()
#define sz(x) (int)(x).size()

#define N 300010
#define int long long

int n,m,a[N],f[N],g[N];

struct lct{//lichao
    #define mid ((l+r)>>1)

    pr tr[N<<2];

    inline int calc(pr u,int x){
        return u.fi*x+u.se;
    }

    inline void build(int k,int l,int r){
        tr[k]={1e7,1e18};

        if(l==r){
            return;
        }

        build(k*2,l,mid);
        build(k*2+1,mid+1,r);
    }

    inline void ref(int k,int l,int r,pr u){
        if(calc(u,a[mid])<calc(tr[k],a[mid])){
            swap(u,tr[k]);
        }

        if(l==r){
            return;
        }

        if(calc(u,a[l])<calc(tr[k],a[l])){
            ref(k*2,l,mid,u);
        }
        if(calc(u,a[r])<calc(tr[k],a[r])){
            ref(k*2+1,mid+1,r,u);
        }
    }

    inline int ask(int k,int l,int r,int x){
        if(l==r){
            return calc(tr[k],x);
        }

        int ans=calc(tr[k],x);

        if(x<=a[mid]){
            ans=min(ans,ask(k*2,l,mid,x));
        }
        else{
            ans=min(ans,ask(k*2+1,mid+1,r,x));
        }

        return ans;
    }

    #undef mid
}ft,gt;

inline void insf(int i){
    ft.ref(1,1,n,{2*(i-1),-2*(i-1)*a[i]+f[i]});
}

inline void insg(int i){
    gt.ref(1,1,n,{2*(i-n),2*(n-i)*a[i]+g[i]});
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    cin>>n>>m;

    rep(i,2,n){
        cin>>a[i];
        a[i]+=a[i-1];
    }

    ft.build(1,1,n);
    gt.build(1,1,n);

    int l=m,r=m;

    insf(l);
    insg(r);

    while(l>1||r<n){
        if(l==1){
            r++;

            g[r]=ft.ask(1,1,n,a[r]);
            insg(r);

            continue;
        }
        if(r==n){
            l--;

            f[l]=gt.ask(1,1,n,a[l]);
            insf(l);

            continue;
        }

        int resl=gt.ask(1,1,n,a[l-1]),resr=ft.ask(1,1,n,a[r+1]);

        if(resl<=resr){
            l--;
            f[l]=resl;
            insf(l);
        }
        else{
            r++;
            g[r]=resr;
            insg(r);
        }
    }

    int ans=min(f[1],g[n]);

    rep(i,1,n){
        ans+=abs(a[i]-a[m]);
    }

    cout<<ans<<"\n";

    return 0;
}
posted @ 2025-07-25 19:00  Lucyna_Kushinada  阅读(55)  评论(0)    收藏  举报