[CEOI2004]锯木厂选址

洛谷题目链接

纪念自己推出了第一道斜率优化!

变量声明:

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

两种方式应该是理解不同,一个是在点的方面思考,一个是在线的方面思考,亲测都对(本题)

 

posted @ 2019-01-04 10:45  模拟退火  阅读(161)  评论(0)    收藏  举报