スロープ最適化hdu 3480 pku 3709 pku 1180 pku 2180


傾き最適化はDP最適化の一種であり、状態遷移方程式をdp[i]=min or max(dp[k]+w[i,k])と仮定し、そのうちの2つの解k 1,k 2(k 1しかしiに対する単調性はk 1f[ii]の場合、G(k 2,k 3)も>f[ii]の場合、k 3はk 2よりも優れているからである.したがって、このときk 2を削除することができ、削除後G(k 1,k 2)は単調に減少するので、このときのf[ii]をG(k 1,k 2)に挿入すると、前のG(k 1,k 2)はいずれも>=f[ii]となり、このときk 2はk 1よりも優れ、後ろのf[ii]はいずれもk 1よりも優れているので、挿入点のその位置で最適なkkがわかる.具体的な実装は両端とも出られるキューで完了する.各ポイントが1回ずつキューを出すので,時間複雑度はO(n)である.以下、hdu 3480で詳細に説明する.
hdu 3480は、大きな集合をm個の小さな集合に分ける、各集合の最大元素を最小元素二乗と最小とする.四角形不等式も使用できます.並べ替え後、状態遷移方程式はdp[i][j]=min(dp[k][j-1]+(a[i]-a[k+1])^2)となり、k 1#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; }
pku 3709は、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単調を保証するだけである.