题目链接: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:
C艹代码:
//斜率优化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[n][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); } }
//四边形不等式优化 #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[n][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); } }