HDU - 3480

发布时间 2023-07-27 14:39:23作者: 星河倒注

\(HDU - 3480\)(斜率优化与四边形不等式优化)

\(name\) \(time(ms)\) \(mem(mb)\) \(lenth\)
四边形不等式优化 \(2074\) \(362.1\) \(731\)
斜率优化 \(1669\) \(166.4\) \(1480\)

斜率优化内存大有是二维数组,没改成一维的原因

1.斜率优化

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 2e9;
const LL LNF = 9e18;
const int mod = 1e9+7;
const int MAXM = 1e5+10;
const int MAXN = 1e4+10;

int val[MAXN], dp[MAXN][MAXN];
int q[MAXN], head, tail;
int ans[100001];

int up(int i, int k1, int k2){
    return (dp[i-1][k1] + val[k1+1]*val[k1+1])-(dp[i-1][k2] + val[k2+1]*val[k2+1]);
}

int down(int k1, int k2){
    return 2*(val[k1+1]-val[k2+1]);
}

int getDP(int i, int j, int k){
    return dp[i-1][k] + (val[j]-val[k+1])*(val[j]-val[k+1]);
}

int main()
{
    int n, m, T;
    cin >> T;
    for(int kase = 1; kase<=T; kase++)
    {
        cin >> n >> m;
        for(int i = 1; i<=n; i++)
            cin >> val[i];
        sort(val+1, val+1+n);
        for(int i = 1; i<=n; i++)
            dp[1][i] = (val[i]-val[1])*(val[i]-val[1]);
        for(int i = 2; i<=m; i++)
        {
            head = tail = 0;
            q[tail++] = i-1;
            for(int j = i; j<=n; j++)
            {
                while(head+1<tail && up(i,q[head+1],q[head])<down(q[head+1], q[head])*val[j]) head++;
                dp[i][j] = getDP(i,j,q[head]);

                while(head+1<tail && up(i,j,q[tail-1])*down(q[tail-1],q[tail-2])<=
                      up(i,q[tail-1],q[tail-2])*down(j,q[tail-1])) tail--;
                q[tail++] = j;
            }
        }
        ans [kase] = dp[m][n];
    }
	for(int i = 1 ; i <= T ; i++){
		cout << "Case " << i << ": " <<ans[i] <<endl; 
	}
	return 0;
}

其实也可以用滚动数组优化一维DP,但我不想,问就是懒(不想改)。

斜率优化的特点是:

1.一般是一维的,斜率优化用到单调队列(可以用数组代替),单调队列二维不好模拟
2.有乘积项,斜率优化的数学推导需要用到一个人尽皆知的数学公式: 完全平方公式。

给出一个基本形式 :$ dp[i] = min (dp[i],A * dp[j] + i * j + c) $

\(tips\) : $ i * j $ 只需要是 \(i\)\(j\) 的乘积项。

知道了什么是斜率优化,剩下的只剩推式子,每一个都不一样,那就先扯一个式子过来(猜猜是那一道题的? 提示:\(HDU\)

现在有这么个式子来示范怎么推式子 都学过数学吧? ,它是怎么得到的你不用管,一般在题目中是没有优化的 \(dp\) 式子。

$ dp[i] = min ( dp[i] , dp[j] + m + ( sum[i] - sum[j] ) ^ {2} ) $ (求的是最小值)

(这是共通的一步,将两个能推向状态 \(i\) 的决策点代入式子经行比较) 假设有两个决策点: \(j\) , \(k\) , 且 \(k<j\) , \(j\) 是较优解 (因为 \(j\) 也可能被淘汰),那么:

$ dp[j] + m + ( sum[i] - sum[j] ) ^ {2} <= dp[k] + ( sum[i] - sum[k] ) ^ {2} $

$ dp[j] + sum[i] ^ {2} - 2 * sum[i] * sum[j] + sum[j] ^ {2} <= dp[k] + sum[i] ^ {2} - 2 * sum[i] * sum[k] + sum[k] ^ {2} $

$ dp[j] + sum[j] ^ {2} - ( dp[k] + sum[k] ^ {2} ) <= 2 * sum[i] * ( sum[j] - sum[k] ) $

2.四边形不等式优化

代码:

#include<bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int t;
int n,m;
int dp[5001][10001];
int pos[5001][10001];
int a[10001];

void ans(){
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++){
		pos[0][i]=1;
	}
	dp[0][0]=0;
	for(int i=1;i<=m;i++){
		dp[i][i]=0;
		pos[i][i]=i;
		pos[i][n+1]=n;
		for(int j=n;j>i;j--){
			for(int k=pos[i-1][j];k<=pos[i][j+1];k++){
				if(dp[i][j]>dp[i-1][k-1]+(a[j]-a[k])*(a[j]-a[k])){
					dp[i][j]=dp[i-1][k-1]+(a[j]-a[k])*(a[j]-a[k]);
					pos[i][j]=k;
				}
			}
		}
	}
	return;
}

int main(){
	int now=0;
	cin>>t;
	while(t--){
		now++;
		cin>>n>>m;
		memset(dp,0x3f,sizeof(dp));
		ans();
		cout<<"Case "<<now<<": "<<dp[m][n]<<endl;
	}
	return 0;
}

比斜率优化短很多,但是不好证明 证明不重要eee~

下面介绍一下四边形不等式优化:

对于形如以下的状态方程:
dp[i][j] = min ( dp[i][j] , dp[i][k-1] + dp[k][j] + cost[k][j] );

或dp[i][j]>dp[i][j-1] 且 dp[i-1][j]>dp[i][j]

( dp[i][k-1] , dp[k][j] 有时会合并成1项)

是具有单调性的:你可以暴力打表对拍:下面给出一组例子:

0 1(列) 2(列) 3(列) 4(列)
1(行) 0 5 25 37
2(行) 0 3 8 17
3(行) 0 0 4 6

像这种转移方程是可以使用四边形不等式优化了。

其实两种优化的本质都是把一些不需要的决策点删除。