[ABC353G] Merchant Takahashi 题解
第一次见这种套路,挺陌生的,可能是我见识太短浅了罢。
题意
有 $ n $ 座城市,分别为 $ 1\sim n $ ,高桥从城市 $ i $ 到城市 $ j $ ,需花费 $ C\times |i-j| $ 的钱。
现在有 $ m $ 个集市,第 $ i $ 个集市在城市 $ t_i $ 举行,高桥如果参加,那么他会获得 $ p_i $ 的钱。
对于 $ 1\le i <m $ ,第 $ i $ 个集市会在第 $ i+1 $ 个集市开始前结束,且忽略高桥移动的耗时。
起初高桥在 $ 1 $ 号城市且他有 $ 10^{ 10^{100} } $ 的钱,请求出在所有集市结束后,高桥能获得的最大利润。
设他最终有 $ 10^{ 10^{100} } +x $ 的钱,则输出 $ x $ 。
思路
首先先列一个暴力动态规划的转移式。
那么设 $ dp_i $ 表示参加第 $ i $ 个集市后最大的收益值。
根据题意,就有 $ dp_i=p_i+\sum_{j=1}^{i-1} max(dp_j-c\times |t_i-t_j|)$ 。
这个暴力转移时间复杂度是 $ O(m^2) $ 的,所以考虑优化。
注意到转移方程中有一个绝对值,那么我们可以将其分类讨论,看一看可不可以将其拆解。
那么第一种情况,若是 $ t_i\ge t_j $ ,则绝对值内的值一定是正的。所以就有
$ dp_i=p_i+\sum_{j=1}^{i-1} max(dp_j-c\times t_i+c\times t_j)$,那么就可以把 $ -c\times t_i $ 这个东西提出来,变为
$ dp_i=p_i-c\times t_i+\sum_{j=1}^{i-1} max(dp_j+c\times t_j)$。
那么后面这个求最大值的部分可以用线段树维护,因为要保证 $ t_i\ge t_j $ ,所以可以考虑让每一个城市作为下标,那么下标为 $ i $ 的位置上维护的值就是 $ \sum_{t_j=i} max(dp_j+c\times t_j)$ 。那么查询就直接查区间为 $ 1\sim t_i$ 的区间最大值。
好,那么这种情况就完成了。
剩下的就是当 $ t_j>t_i $ 时,则绝对值里的值是负数,则有 $ dp_i=p_i+\sum_{j=1}^{i-1} \max(dp_j+c\times t_i-c\times t_j) $ 。提出来就有
$ dp_i=p_i+c\times t_i+\sum_{j=1}^{i-1} max(dp_j-c\times t_j)$。
会发现这里跟第一种情况同理,需要再开一棵线段树,只不过下标为 $ i $ 的位置上维护的值是 $ \sum_{t_j=i} max(dp_j-c\times t_j)$ 。查询就查区间为 $ t_i+1\sim n $ 的区间最大值,当 $ t_i+1>n $ 时返回无穷小。
那么这道题就讨论完了。然后每一次求出 $ dp_i $ 后把两棵线段树的 $ t_i $ 位置上的值更新一下就行了。
然后是初始化,题目说高桥初始在 $ 1 $ 号点,那么就将第一棵线段树的 $ 1 $ 号位置赋值为 $ c $ ,第二棵线段树的 $ 1 $ 号位置赋值为 $ -c $ 就行了。
答案就是所有 $ dp_i $ 的最大值。
时间复杂度 $ O(m\log n) $ 。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long//记得开long long
const int N=3e5+5;
int n,m,t[N],p[N],c,ans,dp[N];//按题目中的原变量命名
struct node{int maxn;}a1[N<<2],a2[N<<2];//a1是第一棵线段树,a2是第二棵线段树
int read()//快读,实在不理解就把它理解为cin或scanf
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void pushup(node s[],int rt)//上传操作
{//把线段树数组当成参数带入函数是为了减少码量
s[rt].maxn=max(s[rt<<1].maxn,s[rt<<1|1].maxn);
}
void build(node s[],int l,int r,int rt,int nw)//建树
{
if(l==r)
{
if(l==1) s[rt].maxn=nw;//下标为1的点赋初值为-c或c
//为方便,把这个初值写成参数nw带入函数中
else s[rt].maxn=-1e17;//赋为最小值
return;
}
int mid=(l+r)>>1;
build(s,l,mid,rt<<1,nw);
build(s,mid+1,r,rt<<1|1,nw);
pushup(s,rt);
}
void update(node s[],int l,int r,int rt,int pos,int w)
{//单点修改
if(l==r)
{
s[rt].maxn=max(s[rt].maxn,w);//取最大值
return;
}
int mid=(l+r)>>1;
if(pos<=mid) update(s,l,mid,rt<<1,pos,w);
else update(s,mid+1,r,rt<<1|1,pos,w);
pushup(s,rt);
}
int query(node s[],int l,int r,int rt,int L,int R)
{//区间查询
if(L>R) return -1e17;
if(L<=l&&R>=r) return s[rt].maxn;
int mid=(l+r)>>1,res=-1e17;
if(L<=mid) res=max(res,query(s,l,mid,rt<<1,L,R));
if(R>mid) res=max(res,query(s,mid+1,r,rt<<1|1,L,R));
return res;
}
signed main()
{
n=read(),c=read(),m=read();
for(int i=1;i<=m;i++) t[i]=read(),p[i]=read();
build(a1,1,n,1,c),build(a2,1,n,1,-c);//建树
for(int i=1;i<=m;i++)
{
int dp_small=p[i]-c*t[i]+query(a1,1,n,1,1,t[i]);
//算t[i]>=t[j]的最大值
int dp_big=p[i]+c*t[i]+query(a2,1,n,1,t[i]+1,n);
//算t[i]<t[j]的最大值
dp[i]=max(dp_small,dp_big);
update(a1,1,n,1,t[i],dp[i]+c*t[i]);
update(a2,1,n,1,t[i],dp[i]-c*t[i]);//对两棵线段树分别修改
ans=max(ans,dp[i]);//更新答案
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号