斜率优化DP

hdu 2993 MAX Average Problem

http://acm.hdu.edu.cn/showproblem.php?pid=2993

这题都整死我了,代码基本上和别人AC的都快一样了还是不对。郁闷死了快。。最后终于发现了错误点自判断斜率时y1*x2 <= y2*x1 如果用整型就会超数据类型肯定会错,而上边的用整型可以过,不过最好用实型因为毕竟k的大小不确定。。。这题的详细解释http://www.docin.com/p-47950655.html 例2 这里在转到0-n个点求斜率的时候不好理解。

数形结合以形住树。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 100007
#define N 100007
using namespace std;

int q[N],sum[N];
double max(double a,double b)
{
    return a > b ? a:b;
}
int GetInt()
{
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    int num = 0;
    while (ch >= '0' && ch <= '9')
    {
        num = num*10 + ch - '0';
        ch = getchar();
    }
    return num;
}
int main()
{
    //freopen("data.in","r",stdin);
    int n,i,k;
    while (~scanf("%d%d",&n,&k))
    {
        sum[0] = 0;
        for (i = 1; i <= n; ++i)
        {
            int x = GetInt();
            sum[i] = sum[i - 1] + x;
        }
        int front = 0,tail = 0;
        q[0] = 0;
        double ans = 0;
        for (i = k; i <= n; ++i)
        {
            int now = i - k;
            while (tail > front)
            {

                int y1 = sum[q[tail]] - sum[q[tail - 1]];
                int x1 = q[tail] - q[tail - 1];
                int y2 = sum[now] - sum[q[tail]];
                int x2 = now - q[tail];
                if (y1*x2 >= y2*x1) tail--;
                else break;
            }
            q[++tail] = now;

            while(front<tail){
                double y1=sum[i] - sum[q[front]];
                double x1=i - q[front];
                double y2=sum[i] - sum[q[front+1]];
                double x2=i - q[front+1];
                if(y1*x2<=y2*x1)front++;
                else break;
            }
            double tmp = (sum[i] - sum[q[front]])*1.0/(i - q[front]);
            if (tmp > ans) ans = tmp;
        }
        printf("%.2lf\n",ans);
    }
    return 0;
}

 

 

hdu  3507 Print Article

http://acm.hdu.edu.cn/showproblem.php?pid=3507

题意:

给定长度为n的整数序列,

对他进行划分,每一段的花费为上述公式:假设从i到j为一段划分则花费为 (∑Ck)^2 + m , i<=k<=j。求一个划分使花费最小。

思路:

这里参考别人的思路,给出个人感觉讲解比较好的连接:

http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html

代码参考:

http://blog.csdn.net/woshi250hua/article/details/7901433

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 100007
#define N 500007
using namespace std;
//freopen("din.txt","r",stdin);

struct point
{
    int x,y;
}pot[N];
int sum[N],q[N];
ll dp[N];
int n,m;
int cross(int x1,int y1,int x2,int y2)
{
    return x1*y2 - x2*y1;
}
int det(point a,point b,point c)
{
    return cross(b.x - a.x,b.y - a.y,c.x - a.x,c.y - a.y);
}
bool CheckIt(int x,int y,int z)//叉积判断点在线段的方向
{
   int mk = det(pot[x],pot[y],pot[z]);
   if (mk <= 0) return true;
   else return false;
}
bool NotBest(int a,int b,int k)
{
    point p0 = pot[a], p1 = pot[b];
    if (pot[a].y - k*pot[a].x >= pot[b].y - k*pot[b].x) return true;
    else return false;
}
int GetInt()//开挂输入
{
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    int num = 0;
    while (ch >= '0' && ch <= '9')
    {
        num = num*10 + ch - '0';
        ch = getchar();
    }
    return num;

}
int main()
{
    //freopen("din.txt","r",stdin);
    int i;
    while (~scanf("%d%d",&n,&m))
    {
        sum[0] = 0;
        for (i = 1; i <= n; ++i)
        {
            //scanf("%d",&sum[i]);
            sum[i] = GetInt();
            sum[i] += sum[i - 1];//累加和
        }

        int front = 0,tail = 0;
        pot[0].x = pot[0].y = 0;//出事化原点
        q[0] = 0;
        for (i = 1; i <= n; ++i)
        {
            pot[i].x = sum[i - 1];
            pot[i].y = dp[i - 1] + sum[i - 1]*sum[i - 1];//记录每个状态的x,y;
            while (front + 1 <= tail && CheckIt(q[tail - 1],q[tail],i)) tail--;//删除不可能为最优的上凸点
            q[++tail] = i;

            while (front + 1 <= tail && NotBest(q[front],q[front + 1],2*sum[i])) front++;//将在下凸折线不是最有的点删除

            int k = q[front];
            dp[i] = pot[k].y - 2*sum[i]*pot[k].x + sum[i]*sum[i] + m;//获取最优值
        }
        printf("%I64d\n",dp[n]);
    }
    return 0;
}

 

hdu 3408  Division

http://acm.hdu.edu.cn/showproblem.php?pid=3480

题意:

给你一个含有n个数的集合,让你划分成M个子集,每个集合里面的(MAX - MIN)^2为这个集合的花费,求总的花费最小。

思路:

首先我们要经整体排序

我们可以得到状态转移方程dp[i][j]表示以j结尾将序列划分成i块的最小花费则有dp[i][j] = min(dp[i - 1][k] + (a[j] - a[k + 1])*(a[j] - a[k + 1]));这样求的话复杂度O(n*m*m)肯定会超时。所以要优化,我们把方程拆开得到dp[i][j] = dp[i - 1][k] + a[j]^2 + a[k + 1]^ - 2*a[j]*a[k + 1];令y = dp[i - 1][k] + a[k + 1]^2 ; x = a[k + 1].

得到 dp[i][j] = y - 2*a[j]*x + a[j]^2;

则套用斜率优化即可。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 5007
#define N 10007
using namespace std;
//freopen("din.txt","r",stdin);

struct point
{
    int x;
    int y;
}pot[N];
int dp[M][N];
int n,m,a[N],q[N];

int cmp(int x,int y)
{
    return x < y;
}
int cross(int x1,int y1,int x2,int y2)
{
    return x1*y2 - x2*y1;
}
int det(point a,point b,point c)
{
    return cross(b.x - a.x,b.y - a.y,c.x - a.x,c.y - a.y);
}
int GetInt()
{
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    int num = 0;
    while (ch >= '0' && ch <= '9')
    {
        num = num*10 + ch - '0';
        ch = getchar();
    }
    return num;

}
bool CheckIt(int x,int y,int z)
{
    int mk = det(pot[x],pot[y],pot[z]);
    if (mk <= 0) return true;
    else return false;
}
bool NotBest(int a,int b,int k)
{
    if (pot[a].y - k*pot[a].x <= pot[b].y - k*pot[b].x) return true;
    else return false;
}
int main()
{
    //freopen("din.txt","r",stdin);
    int i,j,t,k;
    int cas = 1;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d",&n,&m);
        for (i = 1; i <= n; ++i)
        scanf("%d",&a[i]);

        sort(a + 1,a + 1 + n,cmp);

        for (i = 1; i <= n; ++i)//出事划分成一个集合的
        dp[1][i] = (a[i] - a[1])*(a[i] - a[1]);

        pot[0].x = pot[0].y = 0;
        q[0] = 0;
        for (i = 2; i <= m; ++i)//枚举划分的大小
        {
            int front = 0,tail = 0;
            for (j = i; j <= n; ++j)//枚举可能的结束点
            {
                pot[j].x = a[j];
                pot[j].y = dp[i - 1][j - 1] + a[j]*a[j];
                while (front + 1 <= tail && CheckIt(q[tail - 1],q[tail],j)) tail--;
                q[++tail] = j;

                while (front + 1 <= tail && NotBest(q[front + 1],q[front],2*a[j])) front++;

                k = q[front];
                dp[i][j] = pot[k].y - 2*a[j]*pot[k].x + a[j]*a[j];
            }
        }
        printf("Case %d: %d\n",cas++,dp[m][n]);
    }
    return 0;
}

 

hdu 4258 Covered Walkway

http://acm.hdu.edu.cn/showproblem.php?pid=4258

 和上边的两道题目基本上一样,都是划分的题目。

题意:

给你x坐标上的n个点,让你让你两点连线将所有点覆盖,假设我们在a[i]到a[j]连线,则i-j的点都已经覆盖并且这一段的费用为(j - i)^2,求把所有点覆盖后的最小花费。

思路:

由题可得状态转移方程dp[i] = min(dp[j] + (a[i] - a[j + 1])^2 + c) (1<=j < i) 将方程展开后记得

dp[i] = dp[j] + a[j + 1]^2 + a[i]^2 - 2*a[i]*a[j + 1] + c; 令y = dp[j] + a[j + 1]^2 ; x = a[j + 1]; 则有dp[i] = y - 2*a[i]*x + a[i]^2 + c;

套用斜率优化即可。注意数据类型的处理,这里因为超了数据类型错了一次。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 5007
#define N 1000007
using namespace std;
//freopen("din.txt","r",stdin);

struct point
{
    ll x,y;
}pot[N];
ll dp[N];
ll a[N],c;
int q[N];
int n;

ll cross(ll x1,ll y1,ll x2,ll y2)
{
    return x1*y2 - x2*y1;
}
ll det(point a,point b,point c)
{
    return cross(b.x - a.x,b.y - a.y,c.x - a.x,c.y - a.y);
}
bool CheckIt(int x,int y,int z)
{
    ll mk = det(pot[x],pot[y],pot[z]);
    if (mk <= 0) return true;
    else return false;
}
bool NotBest(int a,int b,ll k)
{
    if (pot[a].y - k*pot[a].x <= pot[b].y - k*pot[b].x) return true;
    else return false;
}
ll GetInt()
{
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    ll num = 0;
    while (ch >= '0' && ch <= '9')
    {
        num = num*10 + ch - '0';
        ch = getchar();
    }
    return num;

}
int main()
{
    //freopen("din.txt","r",stdin);
    int i;
    while (~scanf("%d%I64d",&n,&c))
    {
        if (!n && !c) break;
        for (i = 1; i <= n; ++i) a[i] = GetInt();
         //scanf("%I64d",&a[i]);

        int front = 0,tail = 0;
        pot[0].x = pot[0].y = 0;
        q[0] = 0;

        for (i = 1; i <= n; ++i)
        {
            pot[i].x = a[i];
            pot[i].y = dp[i - 1] + a[i]*a[i];

            while (front + 1 <= tail && CheckIt(q[tail - 1],q[tail],i)) tail--;
            q[++tail] = i;

            while (front + 1 <= tail && NotBest(q[front + 1],q[front],2*a[i])) front++;

            int k = q[front];
            dp[i] = pot[k].y - 2*a[i]*pot[k].x + a[i]*a[i] + c;
            //if (i == 1) printf("%I64d %I64d %I64d %I64d %d\n",pot[i].x,pot[i].y,dp[1],a[i],c);
        }
        printf("%I64d\n",dp[n]);
    }
    return 0;
}

 

hdu 2829  Lawrence

http://acm.hdu.edu.cn/showproblem.php?pid=2829

 

基本上和上几个题类似:

题意:

Lawrence要建一条铁路,铁路包括一些收费站(这里描述为一些点,对应着有自己的vale),还有连接两点的线,这条铁路的花费计算方法为:

4*5 + 4*1 + 4*2 + 5*1 + 5*2 + 1*2 = 49.

由于资金材料的限制只能毁掉一些线,使的耗费减小.给定n个点,以及要炸掉m个边求如何炸才能得到最小耗费,输出最小耗费。

思路:

首先我们得到状态转移方程dp[i][j]表示以i结尾炸掉j条路线的最小耗费。则有

dp[i][j] = min(dp[k][j - 1] + cost[k + 1][i]) (1<=k<i). cost[i][j]表示i到j这串数字的乘积和,sum[i]表示1到i为止这些数字的和

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

==>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]

==>由于sum[i]是与i有关的常量,所以很明显,斜率k=sum[i],
设y=dp[k][j-1]-cost[1][k]+sum[k]^2
那么dp[i][j]=y-k*x+cost[1][i]

到这一步就可以套用斜率优化了。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>

#define CL(a,num) memset((a),(num),sizeof(a))
#define iabs(x)  ((x) > 0 ? (x) : -(x))
#define Min(a,b) (a) > (b)? (b):(a)
#define Max(a,b) (a) > (b)? (a):(b)

#define ll long long
#define inf 0x7f7f7f7f
#define MOD 100000007
#define lc l,m,rt<<1
#define rc m + 1,r,rt<<1|1
#define pi acos(-1.0)
#define test puts("<------------------->")
#define maxn 100007
#define M 5007
#define N 1007
using namespace std;
//freopen("din.txt","r",stdin);
int n,m;
int q[N];
struct point
{
    ll x,y;
}pot[N];
ll cost[N],sum[N],a[N],dp[N][N];

ll cross(ll x1,ll y1,ll x2,ll y2)
{
    return x1*y2 - x2*y1;
}
ll det(point a,point b,point c)
{
    return cross(b.x - a.x,b.y - a.y,c.x - a.x,c.y - a.y);
}
bool CheckIt(int x,int y,int z)
{
    ll mk = det(pot[x],pot[y],pot[z]);
    if (mk <= 0) return true;
    else return false;
}
bool NotBest(int a,int b,ll k)
{
    if (pot[a].y - k*pot[a].x <= pot[b].y - k*pot[b].x) return true;
    else return false;
}

ll solve_DP()
{
    int i,j;
    int front,tail;
    pot[0].x = pot[0].y = 0;
    q[0] = 0;

    for (j = 1; j <= m; ++j)
    {
        front = 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 (front + 1 <= tail && CheckIt(q[tail - 1],q[tail],i)) tail--;
            q[++tail] = i;

            while (front + 1 <= tail && NotBest(q[front + 1],q[front],sum[i])) front++;

            int k = q[front];
            dp[i][j] = pot[k].y - sum[i]*pot[k].x + cost[i];
        }
    }
    return dp[n][m];
}
ll GetInt()
{
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    ll num = 0;
    while (ch >= '0' && ch <= '9')
    {
        num = num*10 + ch - '0';
        ch = getchar();
    }
    return num;

}
int main()
{
    //freopen("din.txt","r",stdin);
    int i;
    while (~scanf("%d%d",&n,&m))
    {
        if (!n && !m) break;
        sum[0] = 0;
        for (i = 1; i <= n; ++i)
        {
            //scanf("%I64d",&a[i]);
            a[i] = GetInt();
            sum[i] = sum[i - 1] + a[i];
        }
        cost[0] = 0;
        for (i = 1; i <= n; ++i)
        {
            cost[i] = cost[i - 1] + sum[i - 1]*a[i];
            dp[i][0] = cost[i];//初始化
        }
       /* for (i = 1; i <= n; ++i) printf("%I64d ",cost[i]);
        puts("");*/
        ll ans = solve_DP();
        printf("%I64d\n",ans);
    }
    return 0;
}

 

 

 

posted @ 2012-08-28 11:34  E_star  阅读(301)  评论(0编辑  收藏  举报