斜率优化 hdu3480 pku3709 pku1180 pku2180

      斜率优化是DP优化的一种,假设状态转移方程为dp[i]=min or max (dp[k]+w[i,k]),我们假设取其中两个解k1,k2(不妨设k1<k2),然后得到dp[k1]+w[i,k1]-(dp[k2]+w[i,k2])这个表达式,不妨设结果想取最小值的话,那么上面那个表达式<0就说明k1比k2优。接着经过将左边的一些对i有关的量移到小于号右边,然后就会发现右边的式子在k1<k2时对i具有单调性(如果单调递增的话,i就从1求到n,如果递减,i就从n求到1,状态转移方程变一下)。若此时k2比k1优,那么当求后面的i时,k2一定会比k1优的(因为有单调性)。所以如果我们求dp[i]时的最佳值为kk,那么取下一个i的最优值时,kk前删除的就不用考虑了。

      但是对i的单调性是建立在k1<k2的基础上的,所以如果问题比较极端的话,每次都是最小的最佳,那么将一点改善都没有,所以我们还要寻求别的改善方法。对上面移项后的式子,把右边除i后的项全部除到左边(此时注意符号的变化)。把此时左边的所有项记为G(k1,k2),如果k1<k2且G(k1,k2)<f[i](这是假设上面符号没变,符号变了后可以自己推下)时,k1比k2优.如果存在k1<k2<k3,使得G(k1,k2)<=G(k2,k3)时,k2永远不会是最佳的。因为当G(k1,k2)<=f[ii]时,k1会比k2,当G(k1,k2)>f[ii]时,G(k2,k3)也会>f[ii],此时k3会比k2优.所以此时可以把k2去掉,去掉后G(k1,k2)会保持单调递减,所以把此时的f[ii]插入到G(k1,k2)中,前面的G(k1,k2)都会>=f[ii],此时k2会比k1优,后面的都是k1比k2优,所以插入点的那个位置就可以知道最优的kk.具体的实现用两端都能出的队列即可完成。因为每个点进一次出一次队列,所以时间复杂度为O(n)。下面以hdu3480详细解释一下过程.

      hdu3480求将一个大集合分为m个小集合,使每个集合最大元素减最小元素平方和最小.也可用四边形不等式。排序后,状态转移方程为dp[i][j]=min(dp[k][j-1]+(a[i]-a[k+1])^2),然后k1<k2并且满足dp[k2][j-1]-dp[k1][j-1]+a[k2+1]*a[k2+1]-a[k1+1]*a[k1+1]<2*a[i]*(a[k2+1]-a[k1+1])时,k2会比k1优,所以把k1去掉。把右边的2*(a[k2+1]-a[k1+1])除到左边去(此时注意除的数的正负),然后把整体定义为一个函数G(k1,k2),如果G(k1,k2)<a[i]时,k2会比k1优。如果现在存在k1<k2<k3&&G(k2,k3)<G(k1,k2)时,那么k2是多余的,它永远也不会取到最优值(分别对G(k2,k3)<?a[i]讨论)。所以直接把k2去掉.

#include<iostream>
#include<algorithm>
using namespace std;

int dp[10010][5010],a[10010],q[10010];
int main()
{
	int n,m,i,j,k1,k2,k3,x1,y1,x2,y2,x3,y3,t,test=1,head,tail;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(i=0;i<n;i++)scanf("%d",&a[i]);
		sort(a,a+n);
		for(i=0;i<n;i++)
			dp[i][1]=(a[i]-a[0])*(a[i]-a[0]);
		for(j=2;j<=m;j++)
		{
			head=0,tail=0;q[tail++]=j-2;
			for(i=j-1;i<n;i++)
			{
				while(head+1<tail)
				{
					k1=q[head],k2=q[head+1];
					if(dp[k2][j-1]-dp[k1][j-1]+a[k2+1]*a[k2+1]-a[k1+1]*a[k1+1]<2*a[i]*(a[k2+1]-a[k1+1]))
						head++;
					else break;
				}
				k1=q[head];
				dp[i][j]=dp[k1][j-1]+(a[i]-a[k1+1])*(a[i]-a[k1+1]);
				while(head+1<tail&&i!=n-1)
				{
					k1=q[tail-2],k2=q[tail-1],k3=i;
					x1=a[k1+1],x2=a[k2+1],x3=a[k3+1];
					y1=dp[k1][j-1]+a[k1+1]*a[k1+1];
					y2=dp[k2][j-1]+a[k2+1]*a[k2+1];
					y3=dp[k3][j-1]+a[k3+1]*a[k3+1];
					if((y3-y2)*(x2-x1)<=(y2-y1)*(x3-x2))
						tail--;
					else 
						break;
				}
				q[tail++]=i;
			}
		}
		printf("Case %d: %d/n",test++,dp[n-1][m]);
	}
	return 0;
}

      pku3709将一个含n个数的序列分为几个分为几个至少含有k个数的小序列,每个小序列的数都变为这个小序列最小的那个,每个数改变的和最小的那种情况就是所求。

#include<iostream>
using namespace std;

__int64 sum[110000],dp[110000],q[110000];
int main()
{
	__int64 n,f,i,j,k,head,tail,k1,k2,k3;
	while(scanf("%I64d%I64d",&n,&f)!=EOF)
	{
		sum[0]=0;
		for(i=1;i<=n;i++)
		{
			scanf("%I64d",&j);
			sum[i]=sum[i-1]+j;
		}
		head=tail=0;
		q[tail++]=0;
		for(i=f;i<=n;i++)
		{
			while(head+1<tail)
			{
				k1=q[head],k2=q[head+1];
				if((sum[i]-sum[k1])*(i-k2)<=(sum[i]-sum[k2])*(i-k1))
					head++;
				else 
					break;
			}
			k=q[head];
			dp[i]=1000*(sum[i]-sum[k])/(i-k);
			while(head+1<tail)
			{
				k1=q[tail-2],k2=q[tail-1],k3=i-f+1;
				if((sum[k2]-sum[k1])*(k3-k2)>=(sum[k3]-sum[k2])*(k2-k1))
					tail--;
				else 
					break;
			}
			q[tail++]=i-f+1;
		}

		for(k=0,i=f;i<=n;i++)
			if(dp[i]>k)
				k=dp[i];
		printf("%I64d/n",k);
	}
	return 0;
}

       pku1180

//将任务划分为不同的集合,同一个集合的任务在一个机器上运行
//同一集合的任务都有一个完成时间,但所有的输出时间都一样,
//等于前面集合的运行时间+开机时间S+这一集合上所有任务完成所需时间
//每个任务都有一个花销因子Fi,问怎么划分使得每个任务的Fi*输出时间和最少
//状态转移方程为:f[i]=min(f[k]+(ST[i]-ST[k]+S)*(F[n]-F[k]));
#include<iostream>//#include "stdafx.h" 
using namespace std;//#define int L//#define __int64  LL
const __int64 maxn=11000;
__int64 n,S,f[maxn],q[maxn],T[maxn],F[maxn],ST[maxn],SF[maxn];

__int64 C(__int64 k,__int64 i)
{
	return f[k]+(ST[i]-ST[k]+S)*(SF[n]-SF[k]);
}

int main()
{
	__int64 i,head,tail,k1,k2,k3,x1,x2,x3,y1,y2,y3;
	while(scanf("%I64d%I64d",&n,&S)!=EOF)
	{
		for(i=1;i<=n;i++)
			scanf("%I64d%I64d",&T[i],&F[i]);
		ST[0]=SF[0]=0;
		for(i=1;i<=n;i++)
		{
			ST[i]=ST[i-1]+T[i];
			SF[i]=SF[i-1]+F[i];
		}
		head=tail=0;
		q[tail++]=0;
		f[0]=0;
		for(i=1;i<=n;i++)
		{
			while(head+1<tail)
			{
				k1=q[head],k2=q[head+1];
				if(C(k2,i)<=C(k1,i))
					head++;
				else 
					break;
			}
			f[i]=C(q[head],i);
			while(head+1<tail)
			{
				k1=q[tail-2],k2=q[tail-1],k3=i;
				x1=SF[k1],x2=SF[k2],x3=SF[k3];
				y1=f[k1]-ST[k1]*(SF[n]-SF[k1]);
				y2=f[k2]-ST[k2]*(SF[n]-SF[k2]);
				y3=f[k3]-ST[k3]*(SF[n]-SF[k3]);
				if((y2-y1)*(x3-x2)>=(y3-y2)*(x2-x1))
					tail--;
				else 
					break;
			}
			q[tail++]=i;
		}
		printf("%I64d/n",f[n]);
	}
	return 0;
}

      斜率优化与四边形不等式的区别在于四边形不等式满足k续单调,而斜率只是保证取最优值的k单调。

你可能感兴趣的:(c,优化,ini,任务)