2019杭电多校第一场C Milk (难写的DP)

题意:

一个n*m的网格上有k个牛奶,饮用牛奶 i i i要花费 t i t_i ti的时间,移动到相邻的各种需要花费1,只有在每一行的中点才可以往下移动一格,求饮用 i i i个牛奶需要花费的最小时间( i = 1 , 2 , . . , k ) i=1,2,..,k) i=1,2,..,k).
n , m &lt; = 1 e 9 , k &lt; = 1 e 4 n,m&lt;=1e9,k&lt;=1e4 n,m<=1e9,k<=1e4

题解:

首先离散化,因为往下后不用考虑上面的牛奶,考虑按行dp。
因为只有在中间才能往下移动,所以可以求出每一行从中点开始向左/向右喝 i i i个牛奶并回到/不回到中点的最小花费,然后合并(向左并回到,向右并不回到),(向右并回到,向左不回到),得到该行不回到中点喝牛奶的最小花费,然后和前面行的 回到中点的最小花费 合并,更新答案。合并(向左并回到,向右并回到),再结合前面行的回到中点,得到最终停在当前行的中点并喝 i i i个牛奶的最小花费。
注意第一行特殊处理。
代码是跟着标程写的,用vector的好处是可以方便的存储不确定数量的元素,用数组的话感觉麻烦很多。
ac代码:

#include<bits/stdc++.h>
#define ll long long
#define P pair<int,int>
using namespace std;
const int maxn = 1e4 + 50;
const ll inf = 1e18;
int x[maxn], y[maxn], w[maxn];
ll ans[maxn];
vector<ll> merge(const vector<ll> &a, const vector<ll> &b){//背包的合并
    vector<ll> c(a.size() + b.size() - 1, inf);
    for(int i = 0; i < a.size(); ++i)
        for(int j = 0; j < b.size(); ++j)
            c[i+j] = min(c[i+j], a[i] + b[j]);
    return c;
}
vector<ll> go(const vector<P> &a, int dest){//c[i]代表从a[0].first出发,回到dest/不回到dest并吃i个牛奶的最小花费
    vector<ll> c(a.size(), inf), t(a.size(), inf);
    c[0] = dest==-1 ?0 :abs(dest-a[0].first);
    t[0] = 0;
    for(int i = 1; i < a.size(); ++i){
        int dis = abs(a[i].first - a[i-1].first);//距离
        for(int j = 0; j < i; ++j){
            t[j] += dis;//循环i中t[j]表示走到a[i].first并吃j个牛奶的最小花费
        }
        for(int j = i; j >= 1; --j){
            t[j] = min(t[j], t[j-1] + a[i].second);
        }
        for(int j = 0; j <= i; ++j){//更新当前背包
            if(dest == -1) c[j] = min(c[j], t[j]);
            else c[j] = min(c[j], t[j] + abs(dest - a[i].first));//如果需要折返,要加上折返距离
        }
    }
    return c;
}
vector<ll> r;
vector<P> milk[maxn];//提取每行的牛奶
vector<ll> f[2], g;//滚动背包
int main()
{
    //freopen("02","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;cin>>T;
    while(T--){
        int n, m, k;
        cin>>n>>m>>k;
        r.clear(); r.push_back(1);
        for(int i = 0; i < k; ++i){
            cin>>x[i]>>y[i]>>w[i];
            r.push_back(x[i]);
            ans[i+1] = inf;//初始化答案
        }
        sort(r.begin(), r.end());
        r.erase(unique(r.begin(), r.end()), r.end());//离散化过程

        for(int i = 0; i < r.size(); ++i) milk[i].clear();

        for(int i = 0; i < k; ++i){
            int pos = lower_bound(r.begin(), r.end(), x[i]) - r.begin();
            milk[pos].push_back(P(y[i], w[i]));
        }
        for(int i = 0; i < r.size(); ++i) sort(milk[i].begin(), milk[i].end());//给每行的列排序

        vector<P> t = milk[0];
        t.insert(t.begin(), P(1, 0));
        f[0] = go(t, (m+1)/2);
        g = go(t, -1);
        for(int i = 1; i < g.size(); ++i) ans[i] = min(ans[i], g[i]);

        for(int i = 1; i < r.size(); ++i){
            vector<P>::iterator it = lower_bound(milk[i].begin(), milk[i].end(), P((m+1)/2, 0));
            vector<P> t0(milk[i].begin(), it), t1(it, milk[i].end());
            t0.push_back(P( (m+1)/2, 0 ) );
            reverse(t0.begin(), t0.end());//在左半部分加入中点并使之成为起点
            t1.insert(t1.begin(), P((m+1)/2, 0));//在右半部分插入中点

            vector<ll> f0 = go(t0, (m+1)/2), g0 = go(t0, -1);
            vector<ll> f1 = go(t1, (m+1)/2), g1 = go(t1, -1);

            g0 = merge(f1, g0);
            g1 = merge(f0, g1);

            vector<ll> gg(g0.size());//gg[j]: 当前行取i个并不回到中点的最小花费
            for(int j = 0; j < gg.size(); ++j)
                gg[j] = min(g0[j], g1[j]);
            gg = merge(gg, f[(i-1)&1]);//gg[j]:总体到当前行为止取j个的最小花费

            f[i&1] = merge(f[(i-1)&1], merge(f0,f1));
            for(int j = 0; j < gg.size(); ++j) {
                ans[j] = min(ans[j], gg[j] += r[i]-r[i-1]);//用不回起点的更新答案
                f[i&1][j] += r[i] - r[i-1];
            }
        }
        //cout<<"test:"<<k<<endl;
        for(int i=1;i<=k;++i)
        {
			cout<<ans[i];
			if(i==k) cout<<'\n';
			else cout<<" ";
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值