1. 程式人生 > >洛谷4360[CEOI2004]鋸木廠選址 (斜率優化+dp)

洛谷4360[CEOI2004]鋸木廠選址 (斜率優化+dp)

qwq
我感覺這都已經不算是斜率優化 d p dp 了,感覺更像是qwq一個

下凸殼優化 轉移遞推式子。
qwq

首先我們先定義幾個陣列
s w [ i ] sw[i]

表示 w [ i ] w[i] 的字首和
v a l
[ i ] = w [ i ] × d [ i ] val[i] = w[i]\times d[i]

s u m [ i ] sum[i] 表示 v a l [ i ] val[i] 的字首和。
d i s [ i ] dis[i] 表示 i i 到山腳下的距離
f [ i ] f[i] 表示在 i i 建第一個廠的最小代價。

一個比較容易發現的性質是,廠一定是建在老樹的點上。

因為考慮我們在假設我們現在在一個非老樹的點上,而左邊的 s w sw 大於右邊的 s w sw ,那麼我們現在向左邊移動一定是更優秀的,右邊大也是同理。

我們先考慮只建一個廠,也就是 f [ i ] f[i] 該如何求。

一個比較顯然的遞推 f [ i ] = f [ i 1 ] + s w [ i 1 ] d [ i 1 ] v a l [ i ] f[i]=f[i-1]+sw[i-1]*d[i-1]-val[i]

表示在這個廠建的代價為在之前那個廠的代價+之前的樹木從上個廠移動到這個廠的代價,減去這個廠下去的代價。

然後那我們應該怎麼求 g [ i ] g[i] 呢, g [ i ] g[i] 表示 i i 之前有一個廠,且在 i i 建第二個廠的最小代價。

g [ i ] = m i n ( f [ j ] ( s w [ i ] s w [ j ] ) d i s [ i ] ) g[i]=min(f[j]-(sw[i]-sw[j])*dis[i])

這個式子表示在 i i 這個地方建廠上,是在 j j 的基礎上,又把 j i j到i 的老樹的路程進一步縮短了。

最後我們只需要輸出 m i n ( g [ 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;
}