题意:
一个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
<
=
1
e
9
,
k
<
=
1
e
4
n,m<=1e9,k<=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<<" ";
}
}
}