https://www.luogu.org/problem/P2365

分析:

f[i]表示前i个分组后的最小费用

f[i]=min(f[j]+t[i](c[i]-c[j])+s(c[n]-c[j]));

前j个 i~j的答案 因为分了一组,所以在此之后的组肯定至少有一个等待的s

c是费用前缀和数组

t是时间

直接把min甩掉

\[f[i]=f[j]+t[i]*(c[i]-c[j])+s*(c[n]-c[j])\]

\[f[j]=f[i]-t[i]*(c[i]-c[j])-s*(c[n]-c[j])\]

\[f[j]=f[i]-t[i]*c[i]+t[i]*c[j]-s*c[n]+s*c[j]\]

\(f[j]=(s+t[i])*c[j]+f[i]-t[i]*c[i]-s*c[n]\)
y = k * x + b

因为j已知,所以一次函数中的y和x都要是j为下标的

因此推出上述式子

发现我们要求的是f[i]最小

那么就是b最小

(c[j],f[j])

然后直线过每个点找最小的截距

上凸放弃凸点

下凸可能是凸点

相邻的斜率单调递增的

所以最有解的点左斜率小于k右斜率大于k

单调队列,所以只要考虑右斜率

code:


#include<cstdio>
#include<deque>
using namespace std;
deque<long long>q;
long long t[5005],f[5005];
long long h[5005];
long long dp[5005];//dp[i]表示在i分组完毕后的答案 
int main()
{
    freopen("batch.in","r",stdin);
    freopen("batch.out","w",stdout); 
    long long n,s;
    scanf("%lld%lld",&n,&s);
    for(long long i=1;i<=n;i++)
    {
        scanf("%lld%lld",&t[i],&f[i]);
        t[i]+=t[i-1];//计算时间前缀和 
    }
    for(long long i=n;i>=1;i--)
    {
        h[i]=h[i+1]+f[i];//费用后缀,逆推准备 
    }
    q.push_back(n+1);//初始化,此双端队列基本保证单调 
    for(long long i=n;i>=1;i--)//倒推  
    {
        //保证队列中至少有两个数,且(q[0],q[1])的组合结果比元两者组合的答案大 
        while(q.size()&&q.size()!=1&&h[i]*(t[q[0]-1]-t[q[1]-1])>=dp[q[1]]-dp[q[0]])
        {
            q.pop_front();//就把q[0]弹出去,因为Q【0】在这个组合中会拖后腿 
        }
        //将i加入i~q[0]和q[0]至n 
        dp[i]=dp[q[0]]+h[i]*(s+t[q[0]-1]-t[i-1]); 
        while(q.size()&&q.size()!=1) 
        {
            long long len=q.size();
            //(i,len-1)和(len-1,len-2)比较 
            if((dp[i]-dp[q[len-1]])*(t[q[len-2]-1]-t[q[len-1]-1])<=(dp[q[len-1]]-dp[q[len-2]])*(t[q[len-1]-1]-t[i-1]))
            {//算斜率是哪凸 
                q.pop_back();
            }
            else
            {
                break;
            }
        }
        q.push_back(i);  
    }
    printf("%lld\n",dp[1]); 
    return 0;
}