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