斜率DP题目小结

如果K是常数:单调队列

如果X单调,K单调:斜率优化

若K不单调,X单调:二分在(上)下凸壳上寻找斜率作为dp值。 

若X不单调,K不单调:需要用Splay/CDQ分治维护(上)下凸壳。

题目1:华工G:https://www.nowcoder.com/acm/contest/94/G

DP方程:

题解:X单调,K单调,斜率优化+二分:二分找k作为答案

代码:https://www.nowcoder.com/acm/contest/view-submission?submissionId=25108586


题目2:BZOJ1010:https://www.lydsy.com/JudgeOnline/problem.php?id=1010

DP方程:


,(Y X里面的i应该写成j)

题解:X单调,K单调,斜率优化:

理解与记忆:

出队:斜率式比较:假设j>k,j比k优则有....的那条式子.不断head+1与head比较,得到一个合适的dp(i)值

入队:用斜率逼近来记忆,维护下凸包:判断Ktail tail-1>Ktail i说明K=g(i)逼近时不会碰到tail,不断去尾,最后i入队

模板代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=50005;
ll s[maxn];int q[maxn];ll dp[maxn];ll g[maxn];
int n; ll l;
inline ll Y(int i,int j){return (dp[i]+g[i]*g[i]+2*g[i]*(l+1))-(dp[j]+g[j]*g[j]+2*g[j]*(l+1));}
inline ll X(int i,int j){return (g[i]-g[j])*2;}
inline ll f(int i,int j){return dp[j]+(g[i]-g[j]-l-1)*(g[i]-g[j]-l-1);}
int main()
{
	scanf("%d %lld",&n,&l);ll t=0;int head=0,tail=0;
	memset(dp,0,sizeof(dp));memset(q,0,sizeof(q));memset(s,0,sizeof(s));
	for(int i=1;i<=n;i++)scanf("%lld",&t),s[i]=s[i-1]+t,g[i]=s[i]+i;
	for(int i=1;i<=n;i++)
	{
		while(head<tail&&Y(q[head+1],q[head])<=g[i]*X(q[head+1],q[head]))head++;
		dp[i]=f(i,q[head]);
		while(head<tail&&Y(i,q[tail])*X(q[tail],q[tail-1])<=Y(q[tail],q[tail-1])*X(i,q[tail])  )tail--;
		q[++tail]=i;
	}	
	printf("%lld\n",dp[n]);
} 

题目3:hdu3045:http://acm.hdu.edu.cn/showproblem.php?pid=3045,区间长度不少于m,同POJ3709

DP方程:


题解:x单调,k单调,斜率优化+延迟加入走起

延迟加入的理解:求出dp(j)之后,dp(j)只对dp(j+m),dp(j+m+1)...以后的状态起决策作用

举个例子:求dp(10),m=3  那么决策应该从dp1~dp7选min  

求出dp10之后,才入队dp8,因为dp(11)会用到dp(8),

那什么时候才开始入队呢?i>=2m-1的时候,n=5,m=3理解一下

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=400005;
ll s[maxn];int q[maxn];ll dp[maxn];ll a[maxn];
int n; int m;
inline ll Y(ll i,ll j){return (dp[i]-s[i]+i*a[i+1])-(dp[j]-s[j]+j*a[j+1]);}
inline ll X(ll i,ll j){return (a[i+1]-a[j+1]);}
inline ll f(ll i,ll j){return dp[j]+s[i]-s[j]-a[j+1]*(i-j);}
int main()
{
	while(~scanf("%d %d",&n,&m))
	{
		memset(dp,0,sizeof(dp));	memset(q,0,sizeof(q));	memset(s,0,sizeof(s));
		for(int i=1;i<=n;i++)scanf("%I64d",&a[i]);
		sort(a+1,a+1+n);
		for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
		int head=0,tail=1;
		for(int i=1;i<=n;i++)
		{
			while(head<tail&&Y(q[head+1],q[head])<=1ll*i*X(q[head+1],q[head]))head++;
			dp[i]=f(i,q[head]);
			int late=i-m+1;
			if(i>=2*m-1)
			{
				while(head<tail&&Y(late,q[tail])*X(q[tail],q[tail-1])<=Y(q[tail],q[tail-1])*X(late,q[tail])  )tail--;
				q[++tail]=late;
			}
		}	
		printf("%I64d\n",dp[n]);
	}
} 

题目4:hdu3507:http://acm.hdu.edu.cn/showproblem.php?pid=3507

DP方程:,m是常数


题解:直接斜率优化搞,跟题目2一样

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500005;
ll s[maxn];int q[maxn];ll dp[maxn];
int n,m;
inline ll Y(int i,int j){return dp[i]+s[i]*s[i]-dp[j]-s[j]*s[j];}
inline ll X(int i,int j){return 2*(s[i]-s[j]);}
inline ll f(int i,int j){return dp[j]+m+(s[i]-s[j])*(s[i]-s[j]);}
int main()
{
	while(~scanf("%d %d",&n,&m))
	{
		ll t=0;int head=0,tail=0;
		memset(dp,0,sizeof(dp));	memset(q,0,sizeof(q));	memset(s,0,sizeof(s));
		for(int i=1;i<=n;i++)scanf("%I64d",&t),s[i]=s[i-1]+t;
		for(int i=1;i<=n;i++)
		{
			while(head<tail&&Y(q[head+1],q[head])<=s[i]*X(q[head+1],q[head]))head++;
			dp[i]=f(i,q[head]);
			while(head<tail&&Y(i,q[tail])*X(q[tail],q[tail-1])<=Y(q[tail],q[tail-1])*X(i,q[tail])  )tail--;
			q[++tail]=i;
		}	
		printf("%I64d\n",dp[n]);
	}	
} 


题目5:BZOJ4518:https://www.lydsy.com/JudgeOnline/problem.php?id=4518

DP方程:前j条路分成i段:


题解:X单调,K单调,斜率优化走起,不过这次是二维,注意写法

对每个i跑一遍斜率优化得出dp i j 即可,注意边界!

代码:

//WA 点 初始化的锅dp[0][0]=0;其余为inf 
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3005;
ll a[maxn];ll s[maxn];ll dp[maxn][maxn];ll q[maxn];
inline ll Y(int i,int j,int k){return (dp[i-1][j]+s[j]*s[j])-(dp[i-1][k]+s[k]*s[k]);}
inline ll X(int j,int k){return 1ll*2*(s[j]-s[k]);}
inline ll f(int i,int j,int k){return dp[i-1][k]+(s[j]-s[k])*(s[j]-s[k]);}
int main()
{
	ll n,m;
	scanf("%lld %lld",&n,&m);s[0]=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)dp[i][j]=1000000000000000000;
	dp[0][0]=0;
	for(int i=1;i<=m;i++)
	{
		//memset(q,0,sizeof(q));
		int head=0;int tail=0;
		for(int j=1;j<=n;j++)
		{
			while(head<tail&&Y(i,q[head+1],q[head])<=X(q[head+1],q[head])*s[j])head++;
			dp[i][j]=f(i,j,q[head]);
			while(head<tail&&Y(i,j,q[tail])*X(q[tail],q[tail-1])<=X(j,q[tail])*Y(i,q[tail],q[tail-1]))tail--;
			q[++tail]=j;
		}
	}
	printf("%lld\n",m*dp[m][n]-s[n]*s[n]);	
}

滚动数组的写法:

//WA 点 初始化的锅dp[0][0]=0;其余为inf 
//入队注释那里,乘法貌似溢出了QAQ ull都WA得换成除法才能AC
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3005;
ll a[maxn];
ll s[maxn];
ll dp[2][maxn];
ll q[maxn];
inline ll Y(int i,int j,int k){return (dp[i^1][j]+s[j]*s[j])-(dp[i^1][k]+s[k]*s[k]);}
inline ll X(int j,int k){return 1ll*(s[j]-s[k]);}
inline ll f(int i,int j,int k){return dp[i^1][k]+(s[j]-s[k])*(s[j]-s[k]);}
int main()
{
	ll n,m;
	scanf("%lld %lld",&n,&m);s[0]=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	for(int i=0;i<=1;i++)for(int j=0;j<=n;j++)dp[i][j]=2000000000000000000;
	dp[0][0]=0;
	for(int i=1,t=1;i<=m;t^=1,i++)
	{
		//memset(q,0,sizeof(q));
		int head=0;int tail=0;
		for(int j=1;j<=n;j++)
		{
			while(head<tail&&Y(t,q[head+1],q[head])<=X(q[head+1],q[head])*s[j]*2)head++;
			dp[t][j]=f(t,j,q[head]);
			//while(head<tail && (unsigned long long)Y(t,j,q[tail])*(unsigned long long)X(q[tail],q[tail-1])<=(unsigned long long)X(j,q[tail])*(unsigned long long)Y(t,q[tail],q[tail-1]) )tail--;
			while(head<tail && (double)Y(t,j,q[tail])/(double)X(j,q[tail])<=(double)Y(t,q[tail],q[tail-1])/(double)X(q[tail],q[tail-1] ))tail--;
			q[++tail]=j;
		}
	}
	printf("%lld\n",m*dp[m&1][n]-s[n]*s[n]);	
}


猜你喜欢

转载自blog.csdn.net/animalcoder/article/details/80140664