洛谷 P4360 [CEOI2004]锯木厂选址【模拟退火】


题目:

传送门


题意:

给出 n n n个木材,各自的重量和下一个木材之间的距离
在最后一个位置设有木材厂,同时我们也可以自己设置两个木材厂在任意位置,所有木材只会向编号大的方向移动,直到第一个木材厂为止,花费为移动距离 ∗ * 重量
问最少花费和是多少,在两个地方设木材厂能做到


分析:

模拟退火真是 x s w l xswl xswl
首先分析下花费的求法,假设 a , b a,b a,b表示两个木材厂的位置,就有 = ∑ i = 1 a w i ∗ ∣ a − i ∣ + ∑ i = a + 1 b w i ∗ ∣ b − i ∣ + ∑ i = b + 1 n w i ∗ ∣ n − i ∣ =\sum_{i=1}^aw_i*|a-i|+\sum_{i=a+1}^bw_i*|b-i|+\sum_{i=b+1}^nw_i*|n-i| =i=1awiai+i=a+1bwibi+i=b+1nwini
再设 d i d_i di表示 1 ∼ i − 1 1\sim i-1 1i1的距离,用下乘法分配律可以转换成这个亚子:
d a ∗ ∑ i = 1 a w i + d b ∗ ∑ i = a + 1 b w i + d n + 1 ∗ ∑ i = b + 1 n w i − ∑ i = 1 n w i ∗ d i d_a*\sum_{i=1}^aw_i+d_b*\sum_{i=a+1}^bw_i+d_{n+1}*\sum_{i=b+1}^nw_i-\sum_{i=1}^nw_i*d_i dai=1awi+dbi=a+1bwi+dn+1i=b+1nwii=1nwidi
所有考虑把 w w w d d d用前缀和搞出来,而 a , b a,b a,b用模拟退火一位位移算下就 o j b k ojbk ojbk
比较特殊的是 t l a s t t_{last} tlast是无限趋近与 0.5 0.5 0.5的就够了,因为当 t t t 0.5 0.5 0.5以下时对新的 a , b a,b a,b是不会有影响的,可以,但没必要


代码:

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
#define LL long long
#define down 0.99
using namespace std;
inline LL read() {
    LL d=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){d=d*10+s-'0';s=getchar();}
    return d*f;
}
LL n=read();
LL w[20005],d[20005],s;
struct qwq{
	LL a,b;
	LL get()
	{
		if(a>b) swap(a,b);
		return w[a]*d[a]+(w[b]-w[a])*d[b]+(w[n]-w[b])*d[n+1]-s;
	}
}best,now,odd;
void sa()
{
	double t=n;
	odd.a=rand()%n+1; odd.b=rand()%n+1;
	while(t>0.5)
	{
		int aa=round((2.0*rand()/RAND_MAX-1)*t);
		int bb=round((2.0*rand()/RAND_MAX-1)*t);
		now.a=(odd.a+aa+n)%n+1;
		now.b=(odd.b+bb+n)%n+1;
		LL dif=now.get()-odd.get();
		if(dif<0) odd=now;
		else if(exp(-dif/t)*RAND_MAX>rand()) odd=now;
		if(best.get()>odd.get()) best=odd;
		t*=down;
	}
	return;
}
int main()
{
	srand(19260817);
	for(LL i=1;i<=n;i++) 
	{
		LL a=read(),b=read();
		w[i]=w[i-1]+a;d[i+1]=d[i]+b;
		s+=a*d[i];
	}
	best.a=1;best.b=2;
	for(int i=1;i<=233;i++) sa();
	cout<<best.get();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值