[HNOI2008]玩具装箱-DP斜率优化-单调队列-学习笔记

luogu P3195 https://www.luogu.org/problem/show?pid=3195

BZOJ 1010 http://www.lydsy.com/JudgeOnline/problem.php?id=1010

状态转移方程是显然的:

f[i]=min{f[j]+(i-j-1+s[i]-s[j]-L)^2},0<=j<i
这是一个1D/1D的DP问题。不优化的话是O(n^2)的,肯定会TLE。

考虑优化。显然可以换成斜率优化一般形式:

斜率优化一般形式:f[i]=min{ai*bj+ci+dj}.//这里ai,ci都是一开始就能O(1)确定的,bj,dj都是算出f[j]后可以O(1)确定的
f[i]=min{-2(i+s[i])(j+s[j]+L+1)+(i+s[i])^2+(j+s[j]+L+1)^2+f[j]}  //这一步通过代数运算,把多项式弄成(只和i有关)*(只和j有关)+(只和i有关)+(只和j有关)的形式。
所以在这道题目中,ai=-2*(i+s[i]);
bj=i+s[i]+L+1;ci=(j+s[j])^2;
dj=(i+s[i]+L+1)^2+f[j];
然后考虑:对于要求的状态f [ i ],如果状态j比k更优(不妨设bj<bk),则:

ai*bj+ci+dj<=ai*bk+ci+dk
化简整理得到:
-ai<=(dk-dj)/(bk-bj).
这里把(dk-dj)/(bk-bj)记作Kjk(规定b小的在前面),因为K的形式是个斜率,由此可知可以把b,d分别看作横纵坐标。
即当-ai<=Kjk的时候,从状态j转移更优。
然后继续考虑:

如果bx<by<bz,且Kxy>=Kyz
如果存在-ap<=Kxy,则从状态x转移更优
否则若-ap>Kxy>=Kyz,则从状态z转移更优
综上,若bx<by<bz,且Kxy>=Kyz,那么无论什么时候,都不会有从y转移最优的情况(注意这里并没有用到-ap)。因此可以把y删除。
所以我们要维护一些点,使得相邻的斜率时递增的。也就是一个下凸包。

一般意义上的下凸包维护起来比较麻烦,所以还要具体分析(见下文)。

那么如何找到最优转移状态呢?

考虑(由于K已经是递增的了):正在计算的状态是i,且Kxy<-ai<=Kyz,其中xyz是相邻的。显然因为K是单调递增的,这样的不等式最多只有一个。由左边的等号知:从状态y转移更优。由右边的等号知:从状态y转移更优。
于是我们就是在一个单调递增的数列中找-ai的bound(分不清是upper还是lower),这个总复杂度是O(nlgn)的。

还要注意的是有可能这个等会不存在的情况,需要特判。

当然也不是绝对的,比如说这道题,总复杂度可以做到O(n).

对这道题的具体分析:

首先,bj=j+s[j]+L+1是单调递增的。所以我们每次只是在右边加点,可以用一个单调栈维护。

每个元素进栈出栈最多各一次,总复杂度是O(n)的

其次-ai=2*(i+s[i])是单调递增的。即我们询问的值也是单调递增的。所以在序列左端可以维护一个指针,单调移动。

综上可以维护一个单调队列,左端支持删除,右端支持插入即可。

算法的总复杂度就是O(n).

附上代码,有一些特判比较丑陋。另外,这个题的数据比较大,所以用longlong会超界。

在判断两个斜率相称的时候只能用double类型通过除法来判断。(为此WA了十几次)

堵上丑陋的代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#define debug(x) cerr<<#x<<'='<<x<<" "
#define MAXN 50000
#define ll long long
using namespace std;
int n,L;
ll s[MAXN+10];
inline ll min(ll a,ll b)
{
	return a<b?a:b;
}
inline int kcmp(ll x1,ll y1,ll x2,ll y2,ll x3,ll y3)
{
	double s=(double)(y2-y1)/(y3-y2)*(x3-x2)/(x2-x1);
	if(s>1.0) return 1;
	else return -1;
}
inline int kcmp(ll k,ll x1,ll y1,ll x2,ll y2)
{
	double s=(double)k/(y2-y1)*(x2-x1);
	if(s>1.0) return 1;
	else return -1;
}
struct Deque{
	private:
		ll qx[MAXN+10];
		ll qy[MAXN+10];
		int fp,rp;
	public:
		Deque()
		{
			memset(qx,0,sizeof(qx));
			memset(qy,0,sizeof(qy));
			fp=1;rp=0;
		}
		int push(ll x,ll y)
		{
			if(fp==rp||fp==rp+1)
			{	rp++;qx[rp]=x;qy[rp]=y;return 0;	}
			while(fp!=rp&&kcmp(qx[rp-1],qy[rp-1],qx[rp],qy[rp],x,y)>=0)//kxy>=kyz
				rp--;
			rp++;qx[rp]=x;qy[rp]=y;return 0;
		}
		ll query(ll k,ll ci)//k=-ai
		{
			ll bj,dj;
			if(fp==rp+1)
			{
				bj=L+1;dj=(ll)(L+1)*(L+1);
				return -k*bj+ci+dj;
			}
			if(fp==rp)
				return min(-k*qx[fp]+ci+qy[fp],-k*(L+1)+ci+(ll)(L+1)*(L+1));
			while(fp!=rp&&kcmp(k,qx[fp],qy[fp],qx[fp+1],qy[fp+1])>0)//k>kxy
				fp++;
			return min(-k*qx[fp]+ci+qy[fp],-k*(L+1)+ci+(ll)(L+1)*(L+1));
		}
}deq;
int main()
{
	scanf("%d%d",&n,&L);
	for(int i=1;i<=n;i++)
	{
		int input_number;
		scanf("%d",&input_number);
		s[i]=s[i-1]+input_number;
	}
	ll ai,ci,bj,dj,ans;
	for(int i=1;i<=n;i++)
	{
		ai=-2*(i+s[i]);
		ci=(i+s[i])*(i+s[i]);
		ans=deq.query(-ai,ci);
		bj=i+s[i]+L+1;
		dj=(i+s[i]+L+1)*(i+s[i]+L+1)+ans;
		deq.push(bj,dj);
	}
	printf("%lld\n",ans);
	return 0;
}


根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值