斜率dp 入入门

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 long
const 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 2147493647
using 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值