您的位置:首页 > 其它

ivision (DP_斜率优化|四边形不等式优化)

2013-08-14 21:35 309 查看


Hdu 3480 Division (DP_斜率优化|四边形不等式优化)

分类: 全部博客 ACM_动态规划(DP)2012-08-25
15:58 507人阅读 评论(3) 收藏 举报

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3480

题目大意:给定一个大小为n的集合,要求将集合分成m个子集合,每个集合都有权值,权值为最大值减最小值的平方。m<=5000,n<=10000.

解题思路:还算简单的优化DP吧,因为一眼就能看出是DP而且需要优化。

要将集合分成m个子集合,并且权值和最小,容易想到对集合元素进行排序,能排序的前提是排序完再划分集合,那么此时划分的集合权值总和将和没排序时的相比相等或者更小,并且使得计算更加容易。比如a<b<c<d,那么(d-a)^2 + (c-b)^2 > (b-a)^2 + (d-c)^2.

对于排序完的集合, 状态转移方程为:dp[i][j+1] = Min {dp[k][j] + (arr[i] - arr[k+1])^2}(k < i),朴素的做法复杂度是O(n*m*n),太可怕了。然后呢,就想到斜率优化或者四边形不等式优化了。

这题既能用斜率优化也能用四边形不等式优化,而斜率优化比四边形不等式优化常数更小,速度更快,本文章着重讲斜率优化,据lasten大神说斜率优化能过的题目四边形不等式都能过,不知可信不可信。

dp[i][j+1] = min{dp[k][j] + (arr[i] - arr[k+1]) ^2} = dp[k][j] + arr[i]^2 + arr[k+1]^2 - 2 * arr[i] * arr[k+1].

设y = dp[k][j] + arr[k+1]^2,x = arr[k+1],,那么dp[i][j] = y - 2 * arr[i] * x,转化成这样就可以开始套模版了。

这题我写了很多遍,一拿到题目用四边形就ac了。然后开始用斜率优化写,之前都是写dp[i][j+1] = dp[k][j] + sum[i] + sum[k] - 2 * sum[i][k],这种和只k有关的DP,用别人的模版倒挺顺的,现在换成了和K+1有关就不知道该怎么搞了。晕了好久,直到今天做网络热身赛遇到差不多的题目才想清楚。其实本质是一样的,写法也是一样,但要先处理j==1的的情况。因为这种时候dp[i][1] = (arr[i] - arr[1])^2都是从0转移过来的,可以不跑单调队列。

测试数据:

Input:

10

3 1

1 2 3

3 2

1 2 2

3 2

1 2 4

4 2

4 7 10 1

OutPut:

Case 1: 4

Case 2: 0

Case 3: 1

Case 4: 18

C艹代码:

[cpp] view
plaincopy

//斜率优化DP

#include <stdio.h>

#include <string.h>

#include <algorithm>

using namespace std;

#define MIN 5010

#define MAX 10010

#define INF 2147483647;

struct point{

int x,y;

}pot[MAX];

int dp[MAX][MIN];

int qu[MAX],head,tail;

int n, m, arr[MAX];

int CheckIt(int x,int y,int z) {

point p0 = pot[x],p1 = pot[y],p2 = pot[z];

return (p0.x - p1.x) * (p0.y - p2.y) - (p0.y - p1.y) * (p0.x - p2.x) <= 0;

}

int NotBest(int x,int y,int k) {

point p0 = pot[x],p1 = pot[y];

return p0.y - p0.x * k > p1.y - p1.x * k;

}

int Solve_DP() {

int i, j, k, mmin;

for (i = 1; i <= n; ++i)

dp[i][1] = (arr[i] - arr[1]) * (arr[i] - arr[1]);

for (j = 2; j <= m; ++j) {

head = tail = 0;

for (i = j; i <= n; ++i) {

pot[i].x = arr[i];

pot[i].y = dp[i-1][j-1] + arr[i] * arr[i];

while (head <= tail - 1 &&

CheckIt(qu[tail-1],qu[tail],i)) tail--;

qu[++tail] = i;

while (head + 1 <= tail &&

NotBest(qu[head],qu[head+1],2*arr[i])) head++;

k = qu[head];

dp[i][j] = pot[k].y - 2 * pot[k].x * arr[i] + arr[i] * arr[i];

}

}

return dp
[m];

}

int main()

{

int i, j, k, t, cas = 0;

scanf("%d", &t);

while (t--) {

scanf("%d%d", &n, &m);

for (i = 1; i <= n; ++i)

scanf("%d", &arr[i]);

sort(arr + 1, arr + 1 + n);

int ans = Solve_DP();

printf("Case %d: %d\n", ++cas, ans);

}

}

[cpp] view
plaincopy

//四边形不等式优化

#include <stdio.h>

#include <string.h>

#include <algorithm>

using namespace std;

#define MAX 5010

#define INF 2147483647;

int dp[MAX * 2][MAX];

int n, m, arr[MAX * 2], s[MAX * 2][MAX];

int Solve() {

int i, j, k, mmin;

for (j = 1; j <= m; ++j) s[n + 1][j] = n;

for (i = 1; i <= n; ++i)

dp[i][1] = (arr[i] - arr[1]) * (arr[i] - arr[1]),s[i][1] = 0;

for (j = 2; j <= m; ++j)

for (i = n; i >= 1; --i) {

int mmin = INF;

for (k = s[i][j - 1]; k <= s[i + 1][j] && k < i; ++k)

if (dp[k][j - 1] + (arr[i] - arr[k + 1]) * (arr[i] - arr[k + 1]) < mmin)

mmin = dp[k][j - 1] + (arr[i] - arr[k + 1]) * (arr[i] - arr[k + 1]), s[i][j] = k;

dp[i][j] = mmin;

}

return dp
[m];

}

int main() {

int i, j, k, t, cas = 0;

scanf("%d", &t);

while (t--) {

scanf("%d%d", &n, &m);

for (i = 1; i <= n; ++i)

scanf("%d", &arr[i]);

sort(arr + 1, arr + 1 + n);

int ans = Solve();

printf("Case %d: %d\n", ++cas, ans);

}

}


Hdu 2829 Lawrence (DP_四边形优化|斜率优化)

分类: 全部博客 ACM_好题经典题 ACM_动态规划(DP)2012-08-23
16:42 430人阅读 评论(0) 收藏 举报

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2829

题目大意:给定一个长度为n的序列,至多将序列分成m段,每段序列都有权值,权值为序列内两个数两两相乘之和。m<=n<=1000.

解题思路:经典的DP优化题,可以用四边形不等式优化也可以用斜率优化,我三种方法实现,两种斜率优化,一种四边形不等式,其中复杂度都为n*m,但是常熟略有差异。

状态转移方程很好想,dp[i][j] = min(dp[i][j],dp[k][j-1]+cost[k+1][j])(1<=k<i),这种方程普通写法是n*n*m,当n为1000时运算量为10亿级别,必须优化。

第一种:四边形不等式优化,这种方法是最简单的,主要是减少枚举k的次数。cost[i][j]是某段区间的权值,当区间变大,权值也随之变大,区间变小,权值也随之变小,此时就可以用四边形不等式优化。

我们设s[i][j]为dp[i][j]的前导状态,即dp[i][j] = dp[s[i][j][j-1] + cost[s[i][j]+1][j].之后我们枚举k的时候只要枚举s[i][j-1]<=k<=s[i+1][j],此时j必须从小到大遍历,i必须从大到小。

用这种方法我的代码跑了140ms。

第二种:斜率优化.其实是借鉴大牛大思路,Here,我只是抛砖引玉而已。这种方法的dp和suma数组必须为64位整数,因为平方和会超过32位整数。

用这种方法我的代码跑了350ms。

第三种:斜率优化.其实是借鉴大牛大思路,Here,我只是抛砖引玉而已。其实这题可以作为模板题,斜率优化大抵如此吧。

用这种方法我的代码跑了109ms。

测试数据:

Input:

4 1

4 5 1 2

4 2

4 5 1 2

5 3

1 2 1 2 1

6 4

7 5 3 6 8 9

10 3

1 4 2 7 5 6 8 5 6 9

OutPut:

17

2

92

15

187

C艹代码:

[cpp] view
plaincopy

//四边形不等式

#include <stdio.h>

#include <string.h>

#define MAX 1100

#define INF (1<<30)

int n,m,sum[MAX],cost[MAX][MAX];

int arr[MAX],dp[MAX][MAX],s[MAX][MAX];

void Initial() {

int i, j, k;

for (i = 1; i <= n; ++i)

for (j = 1; j <= n; ++j)

if (j < i) cost[i][j] = 0;

else cost[i][j] = cost[i][j - 1] + arr[j] * (sum[j - 1] - sum[i - 1]);

for (i = 0; i <= n; ++i) {

dp[i][0] = cost[1][i];

s[i][0] = 0,s[n+1][i] = n;

}

}

int Solve_DP() {

int i,j,k;

for (j = 1; j <= m; ++j)

for (i = n; i >= 1; --i) {

dp[i][j] = INF;

for (k = s[i][j-1] ; k <= s[i+1][j]; ++k)

if (dp[k][j-1] + cost[k+1][i] < dp[i][j]) {

s[i][j] = k;

dp[i][j] = dp[k][j-1] + cost[k+1][i];

}

}

return dp
[m];

}

int main()

{

int i,j,k;

while (scanf("%d%d",&n,&m),n+m) {

for (i = 1; i <= n; ++i)

scanf("%d",&arr[i]),sum[i] = arr[i] + sum[i-1];

Initial();

int ans = Solve_DP();

printf("%I64d\n",ans);

}

}

[cpp] view
plaincopy

//sum[i] = arr[1] + .. arr[i]^2

//sum2[i] = arr[1]^2 + .. arr[i]^2;

//dp[i][j] = min{dp[k][j-1] -sum[i] * sum[k] + (suma[k] - sum[k]^2)/2 + (sum[k]^2 - suma[k])/2};

//斜率优化二

#include <stdio.h>

#include <string.h>

#define MAX 1100

#define INF (1<<30)

#define int64 __int64//long long

struct point {

int64 x,y;

}pot[MAX];

int head,tail,qu[MAX];

int n,m,arr[MAX];

int64 sum[MAX],sum2[MAX],dp[MAX][MAX];

void Initial() {

for (int i = 1; i <= n; ++i) {

sum[i] = arr[i] + sum[i-1];

sum2[i] = arr[i] * arr[i] + sum2[i-1];

dp[i][0] = dp[i-1][0] + arr[i] * sum[i-1];

}

}

int CheckIt(point p0,point p1,point p2) {

return (p0.x-p1.x) * (p0.y-p2.y) - (p0.y-p1.y) * (p0.x-p2.x) <= 0;

}

int NotBest(point p0,point p1,int k) {

return p0.y - k * p0.x > p1.y - k * p1.x;

}

int Solve_DP() {

int i,j,k;

for (j = 1; j <= m; ++j) {

head = 0,tail = 0;

qu[tail] = 0;

for (i = j + 1; i <= n; ++i) {

pot[i].x = sum[i-1];

pot[i].y = dp[i-1][j-1] + (sum[i-1] * sum[i-1] + sum2[i-1]) / 2;

while (head <= tail - 1 &&

CheckIt(pot[qu[tail-1]],pot[qu[tail]],pot[i])) tail--;

qu[++tail] = i;

while (head + 1 <= tail &&

NotBest(pot[qu[head]],pot[qu[head+1]],sum[i])) head++;

k = qu[head];

//dp[i][j] = y - k * x + c

dp[i][j] = pot[k].y - sum[i] * pot[k].x + (sum[i] * sum[i] - sum2[i]) / 2;

}

}

return dp
[m];

}

int GetInt() {

char ch = ' ';

while (ch < '0' || ch > '9')

ch = getchar();

int x = 0;

while (ch >= '0' && ch <= '9')

x = x * 10 + ch - '0',ch = getchar();

return x;

}

int main()

{

int i,j,k;

while (scanf("%d%d",&n,&m),n+m) {

for (i = 1; i <= n; ++i)

scanf("%d",&arr[i]),sum[i] = arr[i] + sum[i-1];

Initial();

int ans = Solve_DP();

printf("%d\n",ans);

}

}

[cpp] view
plaincopy

//cost[k+1][i]=cost[1][i]-cost[1][k]-sum[k]*(sum[i]-sum[k])

//dp[i][j]=dp[k][j-1]+cost[1][i]-cost[1][k]-sum[k]*(sum[i]-sum[k])

// =dp[k][j-1]-cost[1][k]+sum[k]^2-sum[i]*sum[k]+cost[1][i]

//斜率优化一

#include <stdio.h>

#include <string.h>

#define MAX 1100

#define INF (1<<30)

struct point {

int x,y;

}pot[MAX];

int head,tail,qu[MAX];

int n,m,arr[MAX],cost[MAX];

int sum[MAX],sum2[MAX],dp[MAX][MAX];

void Initial() {

for (int i = 1; i <= n; ++i) {

sum[i] = arr[i] + sum[i-1];

cost[i] = cost[i-1] + arr[i] * sum[i-1];

dp[i][0] = cost[i];

}

}

int CheckIt(point p0,point p1,point p2) {

return (p0.x-p1.x) * (p0.y-p2.y) - (p0.y-p1.y) * (p0.x-p2.x) <= 0;

}

int NotBest(point p0,point p1,int k) {

return p0.y - k * p0.x > p1.y - k * p1.x;

}

int Solve_DP() {

int i,j,k;

for (j = 1; j <= m; ++j) {

head = 0,tail = 0;

qu[tail] = 0;

for (i = j + 1; i <= n; ++i) {

pot[i].x = sum[i-1];

pot[i].y = dp[i-1][j-1] - cost[i-1] + sum[i-1] * sum[i-1];

while (head <= tail - 1 &&

CheckIt(pot[qu[tail-1]],pot[qu[tail]],pot[i])) tail--;

qu[++tail] = i;

while (head + 1 <= tail &&

NotBest(pot[qu[head]],pot[qu[head+1]],sum[i])) head++;

k = qu[head];

//dp[i][j] = y - k * x + c

dp[i][j] = pot[k].y - sum[i] * pot[k].x + cost[i];

}

}

return dp
[m];

}

int main()

{

int i,j,k;

while (scanf("%d%d",&n,&m),n+m) {

for (i = 1; i <= n; ++i)

scanf("%d",&arr[i]),sum[i] = arr[i] + sum[i-1];

Initial();

int ans = Solve_DP();

printf("%d\n",ans);

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: