[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;
}

posted @ 2024-11-01 18:36  Twilight_star  阅读(10)  评论(0)    收藏  举报