题解:P11049 [IOI 2024] 尼罗河船运
题目传送门
先考虑暴力,容易想到动态规划,先按重量从小到大排序,\(dp_i\) 表示前 \(i\) 个运过去的最小代价,容易得到一下转移式:
解释一下,第一行是第 \(i\) 个货物自己一条船,第二行是 \(i\) 和 \(i-1\) 一起运过去,第三行是 \(i\) 和 \(i-2\) 一起运过去,\(i-1\) 自己过去。那为什么能这么判断,因为假设有 \(4\) 个货物,重量分别为 \(w_1,w_2,w_3,w_4\),有 \(w_1<w_2<w_3<w_4\),那么如果 \(1\) 和 \(4\) 一起运过去,一定不优,因为可以 \(1\) 和 \(2\) 一起,\(3\) 和 \(4\) 一起。所以对于每一个货物,只需要和前面 \(2\) 个比较就可以了。
这个代码复杂度是 \(O(nq)\) 的,可以拿到 \(67\) 分。
考虑如何优化,想到可以把询问排序,离线做。把询问从小到大排,容易发现因为限制越来越宽,所以当 \(d\) 足够大时,答案固定。所以我们维护每个 \(i\) 的三条式子的变化,最初始时我们认为每个 \(i\) 仅满足第一条式子。把 \(w_i\) 从小到大排,\(w_i-w_{i-1}\) 和 \(w_i-w_{i-2}\) 也从小到大排,为了能快速的维护变化。
我们考虑用线段树维护矩阵乘法来实现,这里要定义一个广义矩阵,即把原来矩阵乘法的第 \(i\) 行 \(\times\) 第 \(j\) 列再求和,变为第 \(i\) 行 \(+\) 第 \(j\) 列再取 \(\min\)。
细节如下:
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
res.a[i][j]=min(res.a[i][j],a[i][k]+b.a[k][j]);
考虑用矩阵实现状态转移:
同正常的矩阵乘法一样,这个也具有结合律。那么我们现在只需要用线段树维护每个式子,单点进行修改,最后根节点为答案。
注意矩阵乘法的顺序,以及 \([1,1]\) 的叶子结点的矩阵为空,push_up 时注意判断,注意初始矩阵中的每一个数为无穷大。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1010101;
const int M=4;
struct tt{
ll a[M][M];
void init(){memset(a,0x3f,sizeof(a));}
tt operator*(const tt &b)const{
tt res;
res.init();
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int k=1;k<=3;k++)
res.a[i][j]=min(res.a[i][j],a[i][k]+b.a[k][j]);
return res;
}
}C,t[N];
struct node{ll w,a,b;}s[N];
bool cmp(node a,node b){return a.w<b.w;}
ll f1,f2;
vector<pair<ll,ll> >e,v1,v2;
vector<ll>num;
ll ans[N];
ll ls(ll p){return p<<1;}
ll rs(ll p){return p<<1|1;}
void push_up(ll p){t[p]=t[rs(p)]*t[ls(p)];}
void build(ll p,ll l,ll r){
if(l>r)return;
if(l==r){
t[p]=C;
t[p].a[1][1]=s[l].a;
t[p].a[2][1]=t[p].a[3][2]=0;
return;
}
ll mid=l+r>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
if(l==1&&r==2)t[p]=t[rs(p)];
else push_up(p);
}
void update(ll p,ll l,ll r,ll x,ll idx){
if(x<l||r<x)return;
if(l==r&&l==x){
if(l==2)t[p].a[1][1]=f2,t[p].a[2][1]=f1,t[p].a[3][1]=0;
else{
if(idx==1)t[p].a[1][2]=s[l].b+s[l-1].b;
if(idx==2)t[p].a[1][3]=s[l].b+s[l-2].b+s[l-1].a;
}
return ;
}
ll mid=l+r>>1;
update(ls(p),l,mid,x,idx);
update(rs(p),mid+1,r,x,idx);
if(l==1&&r==2)t[p]=t[rs(p)];
else push_up(p);
}
vector<long long> calculate_costs(vector<int> W,vector<int>A,vector<int>B,vector<int>E){
ll n=W.size(),m=E.size();
for(int i=1;i<=n;i++)s[i].w=W[i-1],s[i].a=A[i-1],s[i].b=B[i-1];
sort(s+1,s+n+1,cmp);
for(int i=1;i<=m;i++)e.push_back({E[i-1],i});
for(int i=2;i<=n;i++)v1.push_back({s[i].w-s[i-1].w,i});
for(int i=3;i<=n;i++)v2.push_back({s[i].w-s[i-2].w,i});
sort(e.begin(),e.end());
sort(v1.begin(),v1.end());
sort(v2.begin(),v2.end());
C.init();
build(1,1,n);
ll i=0,j=0;
for(auto u:e){
f1=s[1].a;
if(s[2].w-s[1].w<=u.first)f2=s[1].b+s[2].b;
else f2=s[1].a+s[2].a;
update(1,1,n,2,0);
while(i<n-1&&v1[i].first<=u.first)update(1,1,n,v1[i].second,1),i++;
while(j<n-1&&v2[j].first<=u.first)update(1,1,n,v2[j].second,2),j++;
ans[u.second]=t[1].a[1][1];
}
for(int i=1;i<=m;i++)num.push_back(ans[i]);
return num;
}
// int main(){
// vector<ll>res;
// res=calculate_costs({15, 12, 2, 10, 21},{5, 4, 5, 6, 3},{1, 2, 2, 3, 2},{5, 9, 1});
// for(auto i:res)cout<<i<<"\n";
// return 0;
// }

浙公网安备 33010602011771号