纪念自己推出了第一道斜率优化!
变量声明:
$sum[i]$表示前$i$棵树的总重
$dis[i]$表示从开头到第$i$棵树的距离
$pay[i]$表示在第$i$处设立厂,花费是多少,只考虑$i$之前的
$f[i]$表示在第$i$处设立第二个厂的总最小花费
那么假设第一个厂建在$j$处,第二个厂建在$i$处,那么$i$~$j$部分的木材运送到$i$处需要的花费则为
$pay[i]-pay[j]-sum[j]*(dis[i]-dis[j])$
那么$i$~$n+1$的花费则为
$pay[n+1]-pay[i]-sum[i]*(dis[n+1]-dis[i])$
而$1$~$j$的花费就是
$pay[j]$
总花费就是上面加起来
$pay[n+1]-sum[j]*(dis[i]-dis[j])-sum[i]*(dis[n+1]-dis[i])$
那么可以得出普通的转移式:(对于$1$~$i$中的每个数$j$)
$f[i]=min(pay[n+1]-sum[j]*(dis[i]-dis[j])-sum[i]*(dis[n+1]-dis[i]))(j<i)$
整理一下:
$1、$去掉$min$,选择性的去括号:
$f[i]=pay[n+1]-sum[j]*dis[i]+sum[j]*dis[j]-sum[i]*(dis[n+1]-dis[i])$
$2、$整理成一次函数$y=kx+b$的形式:
$sum[j]*dis[j]=dis[i]*sum[j]+f[i]-sum[i]*(dis[n+1]-dis[i])$
那么用单调队列维护一下就行了
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 20007
using namespace std;
int n;
int sum[N],dis[N],pay[N];
int que[N],f[N];
int X(int i)
{
return sum[i];
}
int Y(int i)
{
return sum[i]*dis[i];
}
int Get_k(int i,int j)
{
return (Y(i)-Y(j))/(X(i)-X(j));
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&sum[i],&dis[i+1]);
sum[i]+=sum[i-1];
pay[i+1]=pay[i]+sum[i]*dis[i+1];
dis[i+1]+=dis[i];
}
sum[n+1]=sum[n];
int head=1,tail=1;
for(int i=1;i<=n;++i)
{
while(head<tail&&Get_k(que[head],que[head+1])<dis[i])
++head;
f[i]=pay[n+1]-sum[que[head]]*(dis[i]-dis[que[head]])-sum[i]*(dis[n+1]-dis[i]);
while(head<tail&&Get_k(i,que[tail-1])<Get_k(que[tail],que[tail-1]))//Q1
--tail;
que[++tail]=i;
}
int ans=0x3f3f3f3f;
for(int i=1;i<=n;++i)
ans=min(ans,f[i]);
printf("%d",ans);
}
对于代码中$Q1$,应该可以写成如下吧$qwq$(我也不确定):
while(head<tail&&Get_k(i,que[tail])<Get_k(que[tail],que[tail-1]))
--tail;
两种方式应该是理解不同,一个是在点的方面思考,一个是在线的方面思考,亲测都对(本题)