洛谷4360[CEOI2004]锯木厂选址 (斜率优化+dp)

qwq
我感觉这都已经不算是斜率优化\(dp\)了,感觉更像是qwq一个\(下凸壳优化\)转移递推式子。
qwq

首先我们先定义几个数组
\(sw[i]\)表示\(w[i]\)的前缀和
\(val[i] = w[i]\times d[i]\)
\(sum[i]\)表示\(val[i]\)的前缀和。
\(dis[i]\)表示\(i\)到山脚下的距离
\(f[i]\)表示在\(i\)建第一个厂的最小代价。

一个比较容易发现的性质是,厂一定是建在老树的点上。

因为考虑我们在假设我们现在在一个非老树的点上,而左边的\(sw\)大于右边的\(sw\),那么我们现在向左边移动一定是更优秀的,右边大也是同理。

我们先考虑只建一个厂,也就是\(f[i]\)该如何求。

一个比较显然的递推\[f[i]=f[i-1]+sw[i-1]*d[i-1]-val[i]\]

表示在这个厂建的代价为在之前那个厂的代价+之前的树木从上个厂移动到这个厂的代价,减去这个厂下去的代价。

然后那我们应该怎么求\(g[i]\)呢,\(g[i]\)表示\(i\)之前有一个厂,且在\(i\)建第二个厂的最小代价。

\[g[i]=min(f[j]-(sw[i]-sw[j])*dis[i])\]

这个式子表示在\(i\)这个地方建厂上,是在\(j\)的基础上,又把\(j到i\)的老树的路程进一步缩短了。

最后我们只需要输出\(min(g[i])\)即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
#define int long long
using namespace std;
inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}
const int maxn = 1e5+1e2;
int n,m;
int w[maxn],d[maxn];
int f[maxn];
int dis[maxn],val[maxn],sum[maxn];
int sw[maxn];
struct Point{
    int x,y,num;
};
Point q[maxn];
int chacheng(Point x,Point y)
{
    return x.x*y.y-x.y*y.x;
}
bool count(Point i,Point j,Point k)
{
    Point x,y;
    x.x=k.x-i.x;
    x.y=k.y-i.y;
    y.x=k.x-j.x;
    y.y=k.y-j.y;
    if (chacheng(x,y)<=0) return true;
    return false;
}
int head=1,tail=0;
void push(Point x)
{
    while (tail>=head+1 && count(q[tail-1],q[tail],x)) tail--;
    q[++tail]=x;
}
void pop(int lim)
{
    while(tail>=head+1 && q[head+1].y-q[head].y < lim * (q[head+1].x-q[head].x)) head++;
}
int g[maxn];
signed main()
{
  n=read();
  for (int i=1;i<=n;i++) w[i]=read(),d[i]=read(),dis[i]=d[i];
  for (int i=n-1;i>=1;i--) dis[i]+=dis[i+1];
  for (int i=1;i<=n;i++) sw[i]=sw[i-1]+w[i];
  for (int i=1;i<=n;i++) val[i]=w[i]*dis[i];
  for (int i=1;i<=n;i++) sum[i]=sum[i-1]+val[i];
  f[0]=sum[n];
  for (int i=1;i<=n;i++) f[i]=f[i-1]+sw[i-1]*d[i-1]-val[i];
  push((Point){0,f[0],0});
  for (int i=1;i<=n;i++)
  {
     pop((-1)*dis[i]);
     int now = q[head].num;
     g[i]=f[now]-(sw[i]-sw[now])*dis[i];
     push((Point){sw[i],f[i],i});
  }
  int now = q[head].num;
  int ans=1e18;
  ans=f[0];
  for (int i=1;i<=n;i++) ans=min(ans,g[i]);
  cout<<ans;
  return 0;
}

转载于:https://www.cnblogs.com/yimmortal/p/10187000.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值