码迷,mamicode.com
首页 > 其他好文 > 详细

【题解】[CEOI2004]锯木厂选址

时间:2020-05-01 12:56:11      阅读:52      评论:0      收藏:0      [点我收藏+]

标签:观察   while   --   namespace   处理   wro   span   ble   ++   

Link

\(\text{Solution:}\)

注意到题目中的编号是倒着的,于是我们的距离要预处理的是后缀和。

考虑如何\(n^2\)搞:

\(dp[i]\)表示选择\(i\)为第二个中转点的最小代价。

枚举在\(i\)前面的\(j\),代价就是\(dp[i]=\min_{j<i}All-dis[j]*sum[j]-dis[i]*(sum[i]-sum[j])\)

\(All\)是所有树木运输到\(1\)号点的代价。可以理解为,有一部分运输到\(j\)就不用运了,于是把这部分减掉。\(sum\)是重量的前缀和。

枚举前一个点,显然是\(n^2\)的(虽然这样可以过)

考虑优化,先推柿子(令\(S=All\)):

\[dp[i]=S-dis[j]*sum[j]-dis[i]*(sum[i]-sum[j]) \]

\[dis[j]*sum[j]=S-dp[i]-dis[i]*sum[i]+dis[i]*sum[j] \]

\[dis[j]*sum[j]=dis[i]*sum[j]+(S-dp[i]-dis[i]*sum[i]) \]

这时,令:\(y=dis[j]*sum[j],k=dis[i],x=sum[j],b=(S-dp[i]-dis[i]*sum[i])\),一个一次函数式出来了。

显然的斜率优化,但是这里有一个坑,害得我看博客又理解了半天……

其实,如果按照最小化截距来写,会发现这就是一个下凸壳,即使\(dis[i]\)是递减而非递增。但是看了题解后发现,维护的是一个上凸壳。

为什么?

考虑我们究竟要最小化还是最大化。

如果最小化\(b=(S-dp[i]-dis[i]*sum[i])\),则因为\(dp[i]\)前面的符号是负的,所以我们就反其道而行之地把它给最大化了。于是\(\text{Wrong Answer.}\)

所以,实际上我们要最大化截距\(b\),从而最小化\(dp[i]\).

于是我们的任务就变成维护一个上凸包了。观察到\(dis[i]\)递减,于是我们只保留小于\(dis[i]\)的线段。因为后面的最优线段一定是斜率递减的。

我们通过上述分析,可以通过单调队列优化到\(O(n).\)

这题的主要价值在于,注意\(b\)的符号,不是题目中要求\(min\)就一定是下凸包,也不是题目中求最大值就一定是下凸包。看截距的时候要特别留意\(dp[i]\)——我们最关心的值的符号,以此来确定维护上凸壳还是下凸壳。

#include<bits/stdc++.h>
using namespace std;
int n,w[200010],d[200010];
int s[200010],sum[200010];
int dp[200010],ans=0;
int head,tail,q[200010];
int dis[200010],S,A=2147483647;
int Y(int x){return dis[x]*sum[x];}
int X(int x){return sum[x];}
double slope(int x,int y){return (Y(y)-Y(x))/(X(y)-X(x));} 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d%d",&w[i],&dis[i]);
	for(int i=n;i>=1;--i)dis[i]+=dis[i+1];
	for(int i=1;i<=n;++i)ans+=dis[i]*w[i];
	for(int i=1;i<=n;++i)sum[i]=sum[i-1]+w[i];
	//Dis[i]>(dis[k]*sum[k]-dis[j]*sum[j])/(sum[k]-sum[j])
	//Dp[i]=S-dis[j]*sum[j]-dis[i]*(sum[i]-sum[j])
	//Dis[j]*sum[j]=dis[i]*sum[j]+(S-dp[i]-dis[i]*sum[i])
	//最小化后面一坨 斜率是dis[i]
	//head=tail=1;q[head]=1;
	//cout<<ans<<endl;
	for(int i=1;i<=n;++i){
		while(head<tail&&slope(q[head],q[head+1])>=dis[i])head++;
		dp[i]=ans-dis[q[head]]*sum[q[head]]-dis[i]*(sum[i]-sum[q[head]]);
		//cout<<dp[i]<<" ";
		A=min(A,dp[i]);
		while(head<tail&&slope(q[tail-1],q[tail])<=slope(q[tail-1],i))--tail;
		q[++tail]=i;
	}
	printf("%d\n",A);
	return 0;
}

【题解】[CEOI2004]锯木厂选址

标签:观察   while   --   namespace   处理   wro   span   ble   ++   

原文地址:https://www.cnblogs.com/h-lka/p/12812919.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!