您的位置:首页 > 其它

BSOJ2684 cogs 362 -- 【CEOI2004】锯木厂选址 随机化 模拟退火 神级骗分

2016-10-31 15:37 357 查看
[b]想要看随机化的可以看看我的这篇文章

BSOJ2684 cogs 362 -- 【CEOI2004】锯木厂选址[/b]
Description
  从山顶上到山底下沿着一条直线种植了n棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。
  木材只能按照一个方向运输:朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建两个锯木厂,使得传输的费用总和最小。假定运输每公斤木材每米需要一分钱。
  你的任务是编写一个程序,从输入文件中读入树的个数和他们的重量与位置,计算最小运输费用。
Input
  输入的第一行为一个正整数n——树的个数(2≤n≤20000)。树从山顶到山脚按照1,2……n标号。接下来n行,每行有两个正整数(用空格分开)。第i+1行含有:wi——第i棵树的重量(公斤为单位)和 di——第i棵树和第i+1棵树之间的距离,1≤wi≤10000,0≤di≤10000。最后一个数dn,表示第n棵树到山脚的锯木厂的距离。保证所有树运到山脚的锯木厂所需要的费用小于2000000000分。
Output
  输出只有一行一个数:最小的运输费用。
Sample Input
9
1 2
2 1
3 3
1 1
3 2
1 6
2 1
1 2
1 1
Sample Output
26

这是一道老题了。
但是在做这道题的时候,没有使用接受较差解,而是像代码中这样写的。
注意模拟退火是一定要接受一个较差解的,下面有点乱搞了
正解是斜率优化+DP
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
ll n,w[20007]={0},d[20007]={0},sd[20007]={0},sum[20007]={0},co[20007]={0};
ll rx[209]={0},ry[209]={0},ans[209]={0};
inline ll get(ll i,ll j)
{return co[n+1]-sum[j]*(sd[i]-sd[j])-sum[i]*(sd[n+1]-sd[i]);}
void solve()
{
double T=n*1.1;
while(T>1)
{
for(ll i=1;i<=30;i++)
{
for(ll j=1;j<=80;j++)
{
ll nx=(ll)rx[i]*(1-double((rand()%(ll(T+0.5)+1))/double(n))+double((rand()%(ll(T+0.5)+1))/double(n)));
ll ny=(ll)ry[i]*(1-double((rand()%(ll(T+0.5)+1))/double(n))+double((rand()%(ll(T+0.5)+1))/double(n)));
while(ny==nx) ny=(ll)ry[i]*(1-double((rand()%(ll(T+0.5)+1))/double(n))+double((rand()%(ll(T+0.5)+1))/double(n)));
if(nx<1||nx>n||ny<1||ny>n) continue;
ll tmp=get(nx,ny);
if(tmp<ans[i])
{
ans[i]=tmp;
rx[i]=nx;
ry[i]=ny;
}
}
}
T*=0.2;
}
}
int main()
{
scanf("%lld",&n);
srand(time(NULL));
for(ll i=1;i<=n;i++)
{
scanf("%lld%lld",&w[i],&d[i]);
sd[i]=sd[i-1]+d[i-1];
sum[i]=sum[i-1]+w[i];
co[i]=co[i-1]+d[i-1]*sum[i-1];
}
sum[n+1]=sum
;
sd[n+1]=sd
+d
;
co[n+1]=co
+sum
*d
;
for(ll i=1;i<=80;i++)
{
rx[i]=rand()%n+1;
ry[i]=rand()%n+1;
while(ry[i]==rx[i]) ry[i]=rand()%n+1;
ans[i]=get(rx[i],ry[i]);
}
solve();
ll minn=1e100;
for(ll i=1;i<=30;i++) minn=min(minn,ans[i]);
printf("%lld\n",minn);
return 0;
}



刘家骅在2008年国家队论文《浅谈随机化在信息学竞赛中的应用》中仔细讲解了这道题,但是他的代码我找不到了。

分析:
这道题目的标准算法将数据转化为图象,用栈进行处理求出两个矩形的最大覆盖面积,时间复杂度为O(N)。但是,这种算法对能力要求不小,不太容易想到。
我们看下随机化算法在这题上的表现。
首先最容易想到的随机化当然就是直接随机寻找两个点,计算出以这两个点为锯木场时的总运费,多次随机后将总费用最小的输出。
我们可以进行预处理,将计算的时间复杂度降为O(1),那么在时限内我们可以随机几百万次甚至几千万次,但是相对于总状态的四亿来说,寻找到最优解的几率不是很大。
有没有更好的方法呢?
我们刚才是用随机化算法直接出解,准确性不太好,为了增加准确性,那么我们尝试一下用随机化来缩小区域范围。
我们建立一个矩阵P,P[X,Y]表示第一个锯木场建立在X,第二个锯木场建立在Y时的总运费。一开始时,矩阵的边长为N。我们随机寻找一定数量的点(如下左图所示,取点数量应该充分利用时限并且注意效率,由于矩阵的大小一直在变化,推荐使用矩阵大小的定比确定取点数量),计算出它们的值,取其最小点,以这个点为新矩阵的中心,以现在矩阵的边长的3/4的长度为新矩形的边长(如下右图所示),从原来的矩阵中取出一块作为新矩阵的范围(若新矩阵的范围出了原矩阵的边界就将其向里移动到原矩阵内),然后继续在新矩阵中重复这样的操作,直至新矩阵足够小时,我们即可枚举新矩阵上的每一个点,取其中最小值作为答案。

我们惊喜地发现,这种随机化算法对于测试数据能够全部通过!

通过这道题目可以看出,随机化算法的灵活多变使得它的具有更为广阔的运用范围,在许多看似难以入手的地方通过巧妙地运用发光发热。而这样的多变性也使得我们需要灵活恰当地运用随机化算法才能发挥出它的优势。随机化算法并不只是简单地随便乱来,使用随机化算法的时候与其他算法一样值得细细斟酌,需要匠心独运。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息