BJ模拟 巡游计划【线段树】

题目描述:

在遥远的精灵世界,有一个神奇的国度。这个国度由N座城市(从1到N编号)构成,其中1号城市是精灵们的首都,N号城市是精灵们的边疆。精灵国王每年都要从首都出发到边疆去巡游,沿途抚恤她的子民们:她每到一个城市i,就会花A[i]天的时间来巡游这个城市。

精灵世界分布在不同的次元中,所以精灵们只能通过传送阵出行。他们每个城市有一个空间坐标,第i座城市的坐标为P[i],保证P[i]是[1,M]之间的正整数。传送阵的传送距离和传送效率是有限的,精灵国王每次可以从第i座城市出发,到达编号在[i+1,i+k]之间的城市j,并花费|P[i]-P[j]|*B[i]天的时间。

精灵国王想设计一个最优的从1到n的巡游方案,使得她在传送中花费的时间加上巡游花费的时间最少(她也会巡游首都和边疆这两座城市),你能帮帮她吗?

n<=50000;

解题思路:

很容易列出 O(n2) O ( n 2 ) 的dp方程:

fi=min(fj+bj|pipj|)+ai f i = m i n ( f j + b j | p i − p j | ) + a i

假设没有k的限制,那 pj p j 的影响可以根据 pi,pj p i , p j 的大小关系讨论。
pipjfi=min(bjpi+fjbjpj) p i ≥ p j , f i = m i n ( b j p i + f j − b j p j ) ,可以看做是一条覆盖了 (pj,m) ( p j , m ) ,斜率为 bj b j ,截距为 fjbjpj f j − b j p j 的线段。

那么问题就变为了有一些线段,求 x=pi x = p i 取到的最小值,这个问题可以参考bzoj3165,用线段树维护至 O(log2n) O ( l o g 2 n )

现在有了 k k 的限制,我们只需把限制和询问都放到一棵外层的线段树上,遍历线段树每个节点时都重建上面提到的线段树处理更新该节点的询问的答案即可。
时间复杂度是O(nlog3n)

还有做法是用同样的线段树分治法维护斜率dp的凸包,时间复杂度 O(nlog2n) O ( n l o g 2 n )

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();c!='-'&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=2000005;
const ll INF=1e18;
int n,m,k;
int p[N],a[N],b[N];
ll f[N],K[N],B[N];
bool exist[N];
vector<int>upt[N],q[N];
void Add_modify(int k,int l,int r,int x,int y,int id)
{
    if(x>y)return;
    if(x<=l&&r<=y){upt[k].pb(id);return;}
    int mid=l+r>>1;
    if(x<=mid)Add_modify(k<<1,l,mid,x,y,id);
    if(y>mid)Add_modify(k<<1|1,mid+1,r,x,y,id);
}
void Add_query(int k,int l,int r,int x)
{
    q[k].pb(x);if(l==r)return;
    int mid=l+r>>1;
    if(x<=mid)Add_query(k<<1,l,mid,x);
    else Add_query(k<<1|1,mid+1,r,x);
}
void newnode(int k){exist[k]=1,K[k]=0,B[k]=INF;}
void del(int k)
{
    if(!exist[k])return;
    exist[k]=false;
    del(k<<1),del(k<<1|1);
}
ll calc(ll k,ll b,ll x){return k*x+b;}
void update(int k,int l,int r,ll k2,ll b2)
{
    ll k1=K[k],b1=B[k];
    if(calc(k1,b1,l)>=calc(k2,b2,l))swap(k1,k2),swap(b1,b2);
    if(calc(k1,b1,r)<=calc(k2,b2,r)){K[k]=k1,B[k]=b1;return;}
    int mid=l+r>>1;
    if(calc(k1,b1,mid)<=calc(k2,b2,mid))
        K[k]=k1,B[k]=b1,update(k<<1|1,mid+1,r,k2,b2);
    else K[k]=k2,B[k]=b2,update(k<<1,l,mid,k1,b1);
}
void modify(int k,int l,int r,int x,int y,ll ki,ll bi)
{
    if(!exist[k])newnode(k);
    if(x<=l&&r<=y){update(k,l,r,ki,bi);return;}
    int mid=l+r>>1;
    if(x<=mid)modify(k<<1,l,mid,x,y,ki,bi);
    if(y>mid)modify(k<<1|1,mid+1,r,x,y,ki,bi);
}
ll query(int k,int l,int r,int x)
{
    if(!exist[k])return INF;
    ll res=calc(K[k],B[k],x);
    if(l==r)return res;
    int mid=l+r>>1;
    if(x<=mid)res=min(res,query(k<<1,l,mid,x));
    else if(x>mid)res=min(res,query(k<<1|1,mid+1,r,x));
    return res;
}
void solve(int k,int l,int r)
{
    if(!q[k].size())return;
    for(int i=0;i<upt[k].size();i++)
    {
        int j=upt[k][i];
        modify(1,1,m,p[j],m,b[j],f[j]+a[j]-1ll*b[j]*p[j]);
        modify(1,1,m,1,p[j],-b[j],f[j]+a[j]+1ll*b[j]*p[j]);
    }
    for(int i=0;i<q[k].size();i++)f[q[k][i]]=min(f[q[k][i]],query(1,1,m,p[q[k][i]]));
    del(1);int mid=l+r>>1;
    solve(k<<1,l,mid),solve(k<<1|1,mid+1,r);
}
int main()
{
    //freopen("lx.in","r",stdin);
    n=getint(),m=getint(),k=getint();
    for(int i=1;i<=n;i++)p[i]=getint();
    for(int i=1;i<=n;i++)a[i]=getint();
    for(int i=1;i<=n;i++)b[i]=getint();
    for(int i=1;i<=n;i++)
    {
        Add_modify(1,1,n,i+1,min(n,i+k),i);
        if(i!=1)f[i]=INF,Add_query(1,1,n,i);
    }
    solve(1,1,n);
    printf("%lld\n",f[n]+a[n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值