Print Article
阿新 • • 發佈:2020-09-21
題目連結: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
如果上試成立的話,那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; } }