1. 程式人生 > 實用技巧 >Print Article

Print Article

題目連結:https://vjudge.net/problem/HDU-3507#author=0

題意:給出n個數,要求按順序全部取出,每次取出一段所花費的費用為取出一段數的和的平方加m,問最小費用是多少。

思路:很容易想到一個O(n2)的做法,dp[i]=dp[j]+(sum[i]-sum[j])2+m;但n有5*105,肯定會超時。這是就要進行化簡了。

可以假設在求dp[i]是,dp[i]由dp[j]轉換而來比由dp[k]轉換來更優,(j>k)。

那dp[j]+(sum[i]-sum[j])2+m<=dp[k]+(sum[i]-sum[k])2+m

化簡得(dp[j]+sum[j]2)-(dp[k]+sum[k]2

)/(2*(sum[j]-sum[k]))<=sum[i]

如果上試成立的話,那j點就比k點更優,可以把k點淘汰掉。

令:yj=dp[j]+sum[j]*sum[j] xj=2*sum[j]

(yj-yk)/(xj-xk) <= sum[i];

令g[k,j]=(yj-yk)/(xj-xk)

如果對於k<j<i,g[k,j]>g[j,i] 那麼 j 也是可以淘汰的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[500005],sum[500005],q[500005];
ll getx(ll x)
{
    return 2*sum[x];
}
ll gety(ll x)
{
    return dp[x]+sum[x]*sum[x];
}
bool cross(ll x1,ll y1,ll x2,ll y2,ll x3,ll y3)
{
    ll ans=(y3-y1)*(x2-x1)-(y2-y1)*(x3-x1);
    if(ans<=0ll)
        return true;
    return false;
}
bool fun(ll x,ll y,ll i)
{
    if( (dp[x]+sum[x]*sum[x])-(dp[y]+sum[y]*sum[y]) <=2*sum[i]*(sum[x]-sum[y]) )
        return true;
    return false;
}
int main()
{
    ll n,m;
    while(~scanf("%lld%lld",&n,&m))
    {
        for(int i=1; i<=n; i++)
        {
            ll x;
            cin>>x;
            sum[i]=sum[i-1]+x;
            dp[i]=0;
        }
        int head=0,tail=1;
        q[0]=0;
        for(int i=1; i<=n; i++)
        {
            while((head+1<tail)&&fun(q[head+1],q[head],i))
                head++;
            ll x=q[head];
            dp[i]=dp[x]+(sum[i]-sum[x])*(sum[i]-sum[x])+m;
            while((head+1<tail)&&cross(getx(q[tail-2]),gety(q[tail-2]),getx(q[tail-1]),gety(q[tail-1]),getx(i),gety(i)) )
                tail--;
            q[tail++]=i;
        }
        cout<<dp[n]<<endl;
    }
}