2024.5.21

斜率优化 dp 模板题,一共花了 3 days 才 AC。

由于我个人习惯而且写单调队列的边界一直写的都是初始化 l=1,r=0,访问条件 l<=r

按照这种写法,得到了一份 WA 的代码。

结果发现我的队列没有清空。经过清空后得到的 70pts 代码如下:

#include<iostream>
#include<cstdio>
using namespace std;
const int N=5e5+100;
typedef long long ll;
int n,m,q[N],l,r;
ll a[N],dp[N];
ll get_x(ll x)
{
	return 2*a[x];
}
ll get_y(ll x)
{
	return dp[x]+a[x]*a[x];
}
ll slope_fz(ll x,ll y)
{
	return get_y(x)-get_y(y);
}
ll slope_fm(ll x,ll y)
{
	return get_x(x)-get_x(y);
}
int main()
{
	//freopen("sj.in","r",stdin);
	//freopen("4838.out","w",stdout);
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		dp[0]=q[0]=0,l=1,r=0;
		for(int i=1;i<=n;i++) 
		{
			scanf("%lld",&a[i]);
			a[i]+=a[i-1],q[i]=0; 
		}
		for(int i=1;i<=n;i++)
		{
			while(l<=r&&slope_fz(q[l+1],q[l])<=a[i]*slope_fm(q[l+1],q[l])) l++;
			dp[i]=dp[q[l]]+m+(a[i]-a[q[l]])*(a[i]-a[q[l]]);
			while(l<=r&&slope_fz(i,q[r])*slope_fm(q[r],q[r-1])<=slope_fz(q[r],q[r-1])*slope_fm(i,q[r])) r--;
			q[++r]=i;
		}
		printf("%lld\n",dp[n]);
	}
	return 0;
}
/*
10 773
116
1
75
147
80
101
97
15
35
39
*/

多测要清空。

于是拿算法竞赛的代码进行一个比的对,发现区别只有队列的边界。但是我认为我的边界写法没有任何问题,于是与正解进行对拍,得到了一组错误的小数据。

经过 2 days 的推导后找出了问题所在。

正解如此处理边界的原因是要放一个 \(0\) 在队头,在第一个点入队时计算出的是 \(0\) 与它的斜率。如果按照我的方法处理则是计算出第一个和 \(0\) 的斜率。相当于把斜率的计算方式从 \(\frac{y_1-y_2}{x_1-x_2}\) 变为了 \(\frac{x_1-x_2}{y_1-y_2}\),因此会出现错误。

100pts 代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int N=5e5+100;
typedef long long ll;
int n,m,q[N],l,r;
ll a[N],dp[N];
ll get_x(ll x)
{
	return 2*a[x];
}
ll get_y(ll x)
{
	return dp[x]+a[x]*a[x];
}
ll slope_fz(ll x,ll y)
{
	return get_y(x)-get_y(y);
}
ll slope_fm(ll x,ll y)
{
	return get_x(x)-get_x(y);
}
int main()
{
	//freopen("sj.in","r",stdin);
	//freopen("4838.out","w",stdout);
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		dp[0]=a[0]=q[0]=0,l=1,r=1;
		for(int i=1;i<=n;i++) 
		{
			scanf("%lld",&a[i]);
			a[i]+=a[i-1],q[i]=0; 
		}
		for(int i=1;i<=n;i++)
		{
			while(l<r&&slope_fz(q[l+1],q[l])<=a[i]*slope_fm(q[l+1],q[l])) 
			{
			    //printf("%d %d\n",slope_fz(q[l+1],q[l]),a[i]*slope_fm(q[l+1],q[l]));
			    //printf("%d %d\n",q[l+1],q[l]);
			    l++;
			}
			dp[i]=dp[q[l]]+m+(a[i]-a[q[l]])*(a[i]-a[q[l]]);
			while(l<r&&slope_fz(i,q[r])*slope_fm(q[r],q[r-1])<=slope_fz(q[r],q[r-1])*slope_fm(i,q[r])) r--;
			q[++r]=i;
			//printf("%d ",dp[i]);
		}
	    printf("%lld\n",dp[n]);
	}
	return 0;
}
/*
10 773
116
1
75
147
80
101
97
15
35
39

76779
*/

总结:多测清空+单调队列边界处理。

posted @ 2024-08-12 11:09  MinimumSpanningTree  阅读(5)  评论(0)    收藏  举报