斜率优化DP

  • HDU - 3507 Print Article
  • HDU - 2829 Lawrence
  • HDU - 1300 Pearls
  • HDU 3480 Division
  • HYSBZ 1010 玩具装箱toy
  • HYSBZ - 1096 仓库建设

HDU - 3507
入门题。

#include 
#include 
#include 
#include 

using namespace std;
#define ll long long
const int maxn = 5e5 + 10;
ll d[maxn],sum[maxn],dp[maxn];
int n,m;

ll getx(int j,int k)
{   return dp[j] + sum[j]*sum[j] - (dp[k] + sum[k]*sum[k]); }

ll gety(int j,int k)
{   return 2*sum[j]-2*sum[k];   }

ll getval(int i,int j)
{   return dp[j] + (sum[i]-sum[j])*(sum[i]-sum[j]) + m;   }

int que[maxn];

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%I64d",&d[i]);
            sum[i] = d[i];
            if(i) sum[i] += sum[i-1];
            dp[i] = 0;
        }
        int head = 0,tail = 1;
        que[0] = 0;
        for(int i = 1; i <= n; i++)
        {
            while(tail > head+1 && getx(que[head+1],que[head]) <= sum[i]*gety(que[head+1],que[head])) ++head;
            dp[i] = getval(i,que[head]);
            while(tail > head+1 && getx(i,que[tail-1])*gety(que[tail-1],que[tail-2]) <= getx(que[tail-1],que[tail-2])*gety(i,que[tail-1]))  tail--;
            que[tail++] = i;
        }
        printf("%I64d\n", dp[n]);
    }
    return 0;
}

HDU - 2829
题目大意:给你m个炸弹炸铁路,最后问你铁路最小价值和是多少。
思路:可以推出一个很神奇的式子:
dp[i][j]=min(dp[k][j1]+val(k+1,i));
val(k+1,i)=im=k+1d[m](sum[m1]sum[k])
所以有:
val(k+1,i)=im=k+1d[m]sum[m1]im=k+1d[m]sum[k]
然后设
fn[i]=im=1d[m]sum[m1]
发现这个fn[i,k] = fn[i] - fn[k-1],可以O(1)查询,满足区间减法!所以这个时候我们的前提条件就准备好啦。

代码我是设的 sum[i]=i1m=1d[m] 所以fn的表示有一点不同,sum[i]的表示也不同。

#include 
#include 
#include 
#include 

using namespace std;
const int maxn = 1e3+10;
#define ll long long
int d[maxn];
ll dp[maxn][maxn],fn[maxn],sum[maxn];

ll getval(int i,int k,int j)
{
    ll temp = fn[i] - fn[k] - sum[k+1]*(sum[i+1]-sum[k+1]);
    return temp+dp[k][j-1];
}
ll getx(int j,int k,int lev)
{
    ll a = dp[j][lev-1] - fn[j] + sum[j+1]*sum[j+1];
    ll b = dp[k][lev-1] - fn[k] + sum[k+1]*sum[k+1];
    return a-b;
}
int que[maxn];
ll gety(int j,int k)
{   return sum[j+1] - sum[k+1]; }
int n,m;
int main()
{
    //freopen("D:\\in.txt","r",stdin);

    while(scanf("%d%d",&n,&m) && n+m)
    {
        for(int i = 1; i <= n; i++) scanf("%d",&d[i]);
        for(int i = 1; i <= n; i++)
        {
            sum[i] = d[i-1] + sum[i-1];
            fn[i] = sum[i]*d[i] + fn[i-1];
        }
        sum[n+1] = d[n] + sum[n];
        for(int i = 1; i <= n; i++)
            dp[i][1] = getval(i,0,1);
        for(int j = 2; j <= m+1; j++)
        {
            int h = 0, t = 1;
            que[h] = j-1;que[t++] = j;
            for(int i = 0; i <= j; i++) dp[i][j] = 0;
            for(int i = j+1; i <= n;i++)
            {
                int T = sum[i+1];
                while(h+1 < t && getx(que[h+1],que[h],j) <= T*gety(que[h+1],que[h])) h++;
                dp[i][j] = getval(i,que[h],j);
                while(h+11],j)*gety(que[t-1],que[t-2]) <= getx(que[t-1],que[t-2],j)*gety(i,que[t-1])) t--;
                que[t++] = i;
            }
        }

        printf("%I64d\n",dp[n][m+1]);
    }
    return 0;
}

HDU - 1300
题意:这题其实最难的是题意,其实我也就看了个大概
有n种珍珠,每种珍珠有一个需求量,有单价,排在前面的可以用后面的价格买,排在后面的不能用前面的价格买。买的时候是这个式子: cost=(num+10)unit
思路:先列dp方程
dp[i]=dp[k]+val(k+1,i)
val(k+1,i)=c[i](sum[i]sum[k]+10)
然后的事情就很简单了。

#include 
#include 
#include 
#include 

using namespace std;
const int maxn = 105;
int d[maxn],c[maxn];
int sum[maxn];
int dp[maxn],q[maxn];
int getavl(int i,int k)
{
    return dp[k] + c[i]*(sum[i]-sum[k] + 10);
}
int getx(int j,int k){  return dp[j]-dp[k];     }
int gety(int j,int k){  return sum[j]-sum[k];   }
int n;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d%d",&d[i],&c[i]);
            sum[i] = d[i] + sum[i-1];
        }
        int h = 0,t = 0;
        q[t++] = 0;
        for(int i = 1; i <= n; i++)
        {
            int T = c[i];
            while(h+1q[h+1],q[h]) <= gety(q[h+1],q[h])*T) ++h;
            dp[i] = getavl(i,q[h]);
            while(h+1q[t-1])*gety(q[t-1],q[t-2]) <= getx(q[t-1],q[t-2])*gety(i,q[t-1])) --t;
            q[t++] = i;
        }
        cout << dp[n] << endl;
    }
    return 0;
}

HDU - 3480
题意: 把一个序列分成m段,使每段的最大值减最小值的平方和最小。
思路:我们需要先排个序,然后再处理
可以推出dp方程: dp[i][j]=min(dp[k][j1]+(d[i]d[k+1])(d[i]d[k+1]));
由于数组很大,所以需要一下滚动数组。

#include 
#include 
#include 
#include 

using namespace std;

const int maxn = 1e4 + 10;
int d[maxn];
int dp[maxn][2];
int getval(int i,int k,int j)
{
    int last = j==0?1:0;
    return dp[k][last] + (d[i]-d[k+1])*(d[i]-d[k+1]);
}
int getx(int j,int k,int th)
{
    int last = th==0?1:0;
    return dp[j][last]+d[j+1]*d[j+1] - (dp[k][last] + d[k+1]*d[k+1]);
}
int gety(int j,int k){return d[j+1]-d[k+1];}
int q[maxn];
int n,m;
int main()
{
    int t,kase=1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; i++) scanf("%d",&d[i]);
        sort(d + 1, d + n + 1);
        int th = 0,last = 0;
        for(int i = 1; i <= n; i++) dp[i][0] = getval(i,0,th);
        for(int j = 2;  j <= m; j++)
        {
            th = last?0:1;
            for(int i = 1; i <= j; i++) dp[i][th] = 0;
            int h = 0,t = 0;
            q[t++] = j-1; q[t++] = j;
            for(int i = j+1; i <= n; i++)
            {
                int T = d[i]*2;
                while(h+1q[h+1],q[h],th) <= T*gety(q[h+1],q[h])) h++;
                dp[i][th] = getval(i,q[h],th);
                while(h+1q[t-1],th)*gety(q[t-1],q[t-2]) <= getx(q[t-1],q[t-2],th)*gety(i,q[t-1])) t--;
                q[t++] = i;
            }
            last = th;
        }
        printf("Case %d: %d\n", kase++, dp[n][th]);
    }
    return 0;
}

BZOJ - 1010
题意:中文题
思路: dp[i]=dp[j]+i(j+1)+sum[i]sum[j];

#include 
#include 
#include 
#include 

using namespace std;
#define ll long long
const int maxn = 5e4 + 10;
ll d[maxn],sum[maxn],dp[maxn];
int que[maxn];
int n,m;
ll getval(int i,int j)
{
    ll temp = i - (j+1) + sum[i] - sum[j];
    return (ll)dp[j] + (temp-m)*(temp-m);
}
ll getx(int j,int k)
{
    ll a = dp[j] + (j+1+sum[j]+m)*(j+1+sum[j]+m);
    ll b = dp[k] + (k+1+sum[k]+m)*(k+1+sum[k]+m);
    return a-b;
}
ll gety(int j,int k)
{
    return j+sum[j]-k-sum[k];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%lld",&d[i]);
        sum[i] = d[i];
        sum[i] += sum[i-1];
        dp[i] = 0;
    }
    int head = 0, tail = 1;
    que[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        ll T = 2*(i+sum[i]);
        while(tail > head+1 && getx(que[head+1],que[head]) <= T*gety(que[head+1],que[head]))  head++;
        dp[i] = getval(i,que[head]);
        while(tail > head+1 && getx(i,que[tail-1])*gety(que[tail-1],que[tail-2]) <= getx(que[tail-1],que[tail-2])*gety(i,que[tail-1])) tail--;
        que[tail++] = i;
    }
    printf("%lld\n",dp[n]);
    return 0;
}

BZOJ - 1096
中文题+2
思路:
dp[i]=dp[j]+dp[k]fn[i]+fn[k]+d[i][0](sum[i]sum[k])+d[i][2]

#include 
#include 
#include 
#include 

using namespace std;
#define ll long long
const int maxn = 1e6 + 10;
ll d[maxn][3];
ll sum[maxn], fn[maxn];
int que[maxn];
ll dp[maxn];

ll setval(int i,int k)
{
   ll temp = dp[k] - fn[i] + fn[k] + d[i][0]*(sum[i]-sum[k]) + d[i][2];
    return temp;
}
ll fx(int j,int k)
{
    ll a = dp[j] + fn[j] ;
    ll b = dp[k] + fn[k] ;
    return a-b;
}
ll fy(int j,int k)
{
    return sum[j] - sum[k];
}

int main()
{
    //freopen("D:\\in.txt","r",stdin);
    int n;
    scanf("%d",&n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%lld%lld%lld",&d[i][0],&d[i][1],&d[i][2]);
        ll temp = d[i][0]*d[i][1];
        fn[i] = temp;       sum[i] = d[i][1];
        fn[i] += fn[i-1];   sum[i] += sum[i-1];
    }
    int h = 0,t = 1;
    que[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        ll T = d[i][0];
        while(h+1
        dp[i] = setval(i,que[h]);
        while(h+1
        que[t++] = i;
    }
    printf("%lld\n",dp[n]);
    return 0;
}

你可能感兴趣的:(DP)