HDU 3480 - Division - [斜率DP]

时间:2022-12-21 21:15:56

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

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 999999/400000 K (Java/Others)

Little D is really interested in the theorem of sets recently. There’s a problem that confused him a long time.   
Let T be a set of integers. Let the MIN be the minimum integer in T and MAX be the maximum, then the cost of set T if defined as (MAX – MIN)^2. Now given an integer set S, we want to find out M subsets S1, S2, …, SM of S, such that 
HDU 3480 - Division - [斜率DP]
and the total cost of each subset is minimal.

Input

The input contains multiple test cases. 
In the first line of the input there’s an integer T which is the number of test cases. Then the description of T test cases will be given. 
For any test case, the first line contains two integers N (≤ 10,000) and M (≤ 5,000). N is the number of elements in S (may be duplicated). M is the number of subsets that we want to get. In the next line, there will be N integers giving set S. 

Output

For each test case, output one line containing exactly one integer, the minimal total cost. Take a look at the sample output for format. 

Sample Input

2
3 2
1 2 4
4 2
4 7 10 1

Sample Output

Case 1: 1
Case 2: 18

 

题意:

给出含有N元素的集合S,选取M个S的子集,要求满足SU S2 U … U SM = S;

定义一个集合的最大元素为MAX,最小元素为MIN,它的花费为(MAX - MIN)2,现要求所有子集的总花费最少为多少。

 

题解:

先将S内元素从小到大排列,然后将这N个元素的序列分成M组(因为若有重叠元素,必然会使得花费增加);

那么假设dp[i][j]为前i个数分成j组的最小花费,那么求出dp[N][M]即可回答问题;

状态转移方程为dp[i][j] = min{ dp[k][j-1] + (S[i] - S[k+1])2 },j-1≤k<i;

那么当j固定时,计算dp[i][j]时需要枚举k,若k可能取值到a,b两点,且j-1≤a<b<i,

若有 dp[b][j-1] + (S[i] - S[b+1])2 ≤ dp[a][j-1] + (S[i] - S[a+1])2,则b点优于a点;

将上式变形,得到:

b点优于a点 <=> HDU 3480 - Division - [斜率DP]

再然后就是斜率优化的老套路了(斜率优化的详情查看斜率DP分类里之前的文章),就不再赘述。

 

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+5;

int n,m,S[maxn];
int dp[maxn][maxn];
int q[maxn],head,tail;

int up(int a,int b,int j) //g(a,b)的分子部分
{
    return (dp[b][j-1]+S[b+1]*S[b+1])-(dp[a][j-1]+S[a+1]*S[a+1]);
}
int down(int a,int b) //g(a,b)的分母部分
{
    return 2*S[b+1]-2*S[a+1];
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int kase=1;kase<=t;kase++)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%d",&S[i]);
        sort(S+1,S+n+1);

        for(int i=1;i<=n;i++) dp[i][1]=(S[i]-S[1])*(S[i]-S[1]);
        for(int j=2;j<=m;j++)
        {
            head=tail=0;
            q[tail++]=j-1;
            for(int i=j;i<=n;i++)
            {
                while(head+1<tail)
                {
                    int a=q[head], b=q[head+1];
                    if(up(a,b,j)<=S[i]*down(a,b)) head++; //g(a,b)<=S[i]
                    else break;
                }
                int k=q[head];
                dp[i][j]=dp[k][j-1]+(S[i]-S[k+1])*(S[i]-S[k+1]);

                while(head+1<tail)
                {
                    int a=q[tail-2], b=q[tail-1];
                    if(up(a,b,j)*down(b,i)>=up(b,i,j)*down(a,b)) tail--; //g(a,b)>=g(b,i)
                    else break;
                }
                q[tail++]=i;
            }
        }

        printf("Case %d: %d\n",kase,dp[n][m]);
    }
}

注意DP边界的初始化。