斜率dp 入入门

来源:互联网 发布:电脑绘画卡通人物软件 编辑:程序博客网 时间:2024/05/22 12:36

A - Print Article HDU - 3507

题意:一台老旧的打字机,每打出一个单词就会有一个ci.输出一行的费用为(ci)+M(M)现在要输出一篇文章,问费用最少为多少。

分析:dp[i]:前i个单词输出的最少费用
dp[i]=min(dp[j],dp[j]+(sum[i]sum[j])2)

时间复杂度为n*n,n的范围是5e5,会T
这个时候就需要优化了,把min的范围变小就好了。

假设k< j < i,j比k优,则可以得到
dp[j]+sum[j]2(dp[k]+sum[k]2)/(2sum[j]2sum[k])<=sum[i]
yj=dp[j]+sum[j]2,xj=2sum[j]
g(j,k)=yj-yk/(xj-xk),如果g(j,k)<=sum[i],则j比k优,那么k就可以去掉了
如果g(i,j) < g(j,k)
if(g(j,k)<=sum[i]) 表示i比j优,j比k优,j,k可以去掉
if(g(i,j)>sum[i]) 表示j比i优,k比j优,i,j可以去掉
综合之上,if(g(i,j)< g(j,k)) 则j可以去掉
用队列维护,这样的话,我们没去掉的点,也就是队列中的点连起来就是下凸形。
因为g(i,j)相当于斜率,也就是一阶导,当任意g(j,k)<=g(i,j),也就是二阶导大于或者等于0.这个时候,函数呈上凹也就是下凸形

#include <iostream>#include <algorithm>#include <cstdio>#include <cmath>#include <cstring>using namespace std;#define ll long longconst int maxn = 5e5+10;ll dp[maxn],q[maxn],sum[maxn];ll y(int j,int k){    return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k];}ll x(int j,int k){    return 2*(sum[j]-sum[k]);}ll getdp(int i,int j){    return dp[j]+(sum[i]-sum[j])*(sum[i]-sum[j]);}int main(){    ll n,m;    while(scanf("%I64d %I64d",&n,&m)!=EOF)    {        for(int i=1;i<=n;i++)            scanf("%I64d",&sum[i]);        sum[0]=dp[0]=0;        for(int i=1;i<=n;i++)            sum[i]+=sum[i-1];        int head=0,tail=0;        q[tail++]=0;        for(int i=1;i<=n;i++)        {            while(head+1<tail&&y(q[head+1],q[head])<=sum[i]*x(q[head+1],q[head]))                head++;            dp[i]=getdp(i,q[head])+m;            while(head+1<tail&&y(i,q[tail-1])*x(q[tail-1],q[tail-2])<=y(q[tail-1],q[tail-2])*x(i,q[tail-1]))                tail--;            q[tail++]=i;        }        printf("%I64d\n",dp[n]);    }    return 0;}

ps:做题做着做着,忽然想队列优化while判断中,能不能直接用getdp()来表示呢,那样就不用化简了,这样

 while(head+1<tail&&getdp(i,q[head+1])<=getdp(i,q[head]))                head++;            dp[i]=getdp(i,q[head])+m;            while(head+1<tail&&getdp(i,q[tail-2])*getdp(i,i)<=getdp(i,q[tail-1])*getdp(i,q[tail-1]))                tail--;

第一个while还可以改,但是第二个就不行了呀,因为g(j,k)和i并没有关系,不能直接用i。

2016年沈阳区域赛I 题 The Elder UVALive - 7620

题意:给你一棵树,节点从1到n,代表n个城市。1是首都,现在要从每个城市分别送信到首都。邮差可以在中途经过的城市选择继续送信或者给别的邮差送,继续送信的时间是lena+lenb2,让别的邮差送的话,中间需要p时间来交接,时间花费是lena2+lenb2+p,每个城市送信到首都的花费要最小,这些花费中最大的是?

分析:就是斜率dp嘛
状态转移:dp[i]=min(dp[j]+(sum[i]sum[j])2+p);
这里有一点不同,就是这个斜率dp是在树上的,要注意还原。。。

#include <algorithm>#include <iostream>#include <cstring>#include <cstdio>#include <string>#include <cmath>#include <queue>#include <set>#include <map>#include <stack>//#pragma comment(linker,"/STACK:102400000,102400000")#define IN freopen("D:\\in.txt","r",stdin)#define OUT freopen("D:\\out.txt","w",stdout)#define ll long long#define mem(a,b) memset(a,b,sizeof(a))#define inf 2147493647using namespace std;const int maxn = 1e5+10;int n;ll p;struct point{    int pos,time,z;};stack<point> st;struct node{    int pre,to;ll w;}e[maxn*2];int head[maxn],vis[maxn],q[maxn];ll dp[maxn],sum[maxn];int head1=0,tail=0;void init(){    mem(head,-1);mem(vis,0);mem(e,0);mem(sum,0);mem(dp,0);}void addedge(int h,int from,int to,ll w){    e[h].to=to;e[h].pre=head[from];e[h].w=w;head[from]=h;}ll y(int j,int k){    return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k];}ll x(int j,int k){    return 2ll*(sum[j]-sum[k]);}ll getdp(int i,int j){    return dp[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+p;}int dfs_num=0;void dfs(int u){    int ti=++dfs_num;    while(head1+1<tail&&y(q[head1+1],q[head1])<=sum[u]*x(q[head1+1],q[head1]))        head1++;    dp[u]=getdp(u,q[head1]);    while(head1+1<tail&&y(u,q[tail-1])*x(u,q[tail-2])<=y(u,q[tail-2])*x(u,q[tail-1])){        tail--;        point a;        a.pos=tail;        a.time=dfs_num;        a.z=q[tail];        st.push(a);    }    q[tail++]=u;    int nowhead1=head1,nowtail=tail;    for(int i=head[u];i>-1;i=e[i].pre)    {        if(vis[e[i].to]) continue;        vis[e[i].to]=1;        head1=nowhead1,tail=nowtail;        while(!st.empty())        {            point t=st.top();            if(t.time<=ti) break;            q[t.pos]=t.z;            st.pop();        }        sum[e[i].to]=sum[u]+e[i].w;        dfs(e[i].to);    }}int main(){    int T;    scanf("%d",&T);    while(T--)    {        init();        int h=0;        scanf("%d %lld",&n,&p);        for(int i=0;i<n-1;i++)        {            int from,to;ll w;            scanf("%d %d %lld",&from,&to,&w);            addedge(h,from,to,w);h++;            addedge(h,to,from,w);h++;        }        vis[1]=1;        while(!st.empty()) st.pop();        head1=0,tail=0;        dfs_num=0;        dp[1]=-p;        for(int i=head[1];i>-1;i=e[i].pre)        {            if(vis[e[i].to]) continue;            vis[e[i].to]=1;            sum[e[i].to]=sum[1]+e[i].w;            while(!st.empty()) st.pop();            head1=0,tail=0;            q[tail++]=1;            dfs(e[i].to);        }        ll ans=0;        for(int i=1;i<=n;i++) ans=max(ans,dp[i]);        cout<<ans<<endl;    }    return 0;}
原创粉丝点击