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; }