[NOIP2012模拟10.25] 旅行 [构造]

给定一个n行m列的字符矩阵。’.’代表空地,’X’代表障碍。
每秒钟可以往上下左右四个方向其中的一个移动一格(不能往障碍里面撞)。
起点和终点可以在空地里面随机选择(可以重合)。
求从起点移动到终点最短耗时的期望。
每一行、每一列至多有一个障碍。障碍不在对角线方向相邻。 2 ≤ n , m ≤ 1 0 3 2\le n,m\le10^3 2n,m103
I n p u t Input Input
第一行两个整数n, m。
接下来n行,每行m个字符’.’或’X’。
O u t p u t Output Output
平均耗时。保留4位小数,四舍五入。

E E E为空地数, P P P为障碍数
∑ M i n D i s u , v E 2 \frac{\sum MinDis_{u,v}}{E^2} E2MinDisu,v

当然不可能枚举 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2
解法有可能是枚举 x , y x,y x,y然后利用之前计算的结果

两个点 u , v u,v u,v之间的最短距离大于它们的曼哈顿距离当且仅当在 x 1 , x 2 x_1,x_2 x1,x2 y 1 , y 2 y_1,y_2 y1,y2之间有一串墙,它们的 x x x覆盖了 x 1 x_1 x1~ x 2 x_2 x2 y y y覆盖了 y 1 y_1 y1~ y 2 y_2 y2

会增加多少?
显然当且仅当障碍长成
u· · ·
x· · ·
· · · ·
· x· ·
· · · ·
· · x·
· · · ·
· · · x
· · · v
横过来也行, x x x之间距离大一点也行, u u u v v v再上一点下一点也没关系, x x x多几列也好

然后用所有空地对的曼哈顿距离加上这些增加的部分就好了。
曼哈顿距离和是多少?
复杂的曼哈顿距离问题一般要分解为 x x x方向和 y y y方向的距离。
d i s u , v = Δ x + Δ y dis_{u,v}=\Delta x+\Delta y disu,v=Δx+Δy
枚举行 i , j i,j i,j ∑ Δ y x y = i , x y = j = E y = i ∗ E y = j ∗ ∣ i − j ∣ \sum \Delta y_{x_{y=i},x_{y=j}}=E_{y=i}*E_{y=j}*|i-j| Δyxy=i,xy=j=Ey=iEy=jij
枚举列 i , j i,j i,j ∑ Δ x y x = i , y x = j = E x = i ∗ E x = j ∗ ∣ i − j ∣ \sum \Delta x_{y_{x=i},y_{x=j}}=E_{x=i}*E_{x=j}*|i-j| Δxyx=i,yx=j=Ex=iEx=jij

增加了多少?
x 1 x_1 x1~ x 2 x_2 x2间是否每个 x i x_i xi都有一个障碍并且障碍的 y y y单调递增或者递减。
记录一下具体是递增还是递减,障碍的 m i n y min_y miny m a x y max_y maxy
然后就可以计算纵方向距离多 2 2 2的点对数量了。
y y y同理。
考虑到同一行/列不能有两个障碍,所以 x x x y y y的答案不会有重复部分。
具体的实现从障碍来入手会方便得多。

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<cstring>
#include<ctime>
using namespace std;
char ch=0;
double N,M,K=0;
double EmptyN[1005]={};
double EmptyM[1005]={};
double NPos[1005]={};
double MPos[1005]={};
bool Pt[1005][1005]={};
double Ans=0;
int main()
{
	scanf("%lf%lf ",&N,&M);
	for(int i=1;i<=N;++i)
	{
		while(ch!='.'&&ch!='X')ch=getchar();
		for(int j=1;j<=M;++j)
		{
			if(ch=='X')Pt[i][j]=1,NPos[i]=j,MPos[j]=i;
			else ++EmptyN[i],++EmptyM[j],++K;
			ch=getchar();
		}
	}
	for(int i=1;i<N;++i)
	for(int j=i+1;j<=N;++j)
	Ans+=2.0f*EmptyN[i]*EmptyN[j]*(j-i);
	
	for(int i=1;i<M;++i)
	for(int j=i+1;j<=M;++j)
	Ans+=2.0f*EmptyM[i]*EmptyM[j]*(j-i);
	
	for(int i=1;i<=N;++i)
    {
        if(!NPos[i])continue;
        Ans+=4.0f*(NPos[i]-1.0)*(M-NPos[i]);
        for(int j=i-1;j&&NPos[j]&&NPos[j]<NPos[j+1];--j)Ans+=4.0f*(NPos[j]-1.0)*(M-NPos[i]);
        for(int j=i+1;j<=N&&NPos[j]&&NPos[j]<NPos[j-1];++j)Ans+=4.0f*(NPos[j]-1.0)*(M-NPos[i]);
    }
    
	for(int i=1;i<=M;++i)
    {
        if(!MPos[i])continue;
        Ans+=4.0f*(MPos[i]-1.0)*(N-MPos[i]);
        for(int j=i-1;j&&MPos[j]&&MPos[j]<MPos[j+1];--j)Ans+=4.0f*(MPos[j]-1.0)*(N-MPos[i]);
        for(int j=i+1;j<=M&&MPos[j]&&MPos[j]<MPos[j-1];++j)Ans+=4.0f*(MPos[j]-1.0)*(N-MPos[i]);
    }
    printf("%.4f",Ans/K/K);
	
	return 0;
}

对拍参照的std

数据生成器
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<sstream>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
ll GenRand(const ll Lim1,ll Lim2)
{
	++Lim2;
	ll ret=Lim1;
	int t=0;
	while(t<100)
	{
		if(rand()/(RAND_MAX+1.0)<0.1)break;
		ret+=rand();
		ret%=Lim2;
		++t;
	}
	while(ret<Lim1)ret+=Lim1;
	ret%=Lim2;
	return ret;
}
int N,M;
bool Pd[1005]={},Px[1005]={};
stringstream ss;

int main( int argc, char *argv[] )
{ 
    int seed=time(NULL);
    if(argc > 1)//如果有参数
    {
        ss.clear();
        ss<<argv[1];
        ss>>seed;//把参数转换成整数赋值给seed
    }
    srand(seed);
	N=GenRand(2,1000),M=GenRand(2,1000);
	printf("%d %d\n",N,M);
	for(int i=1;i<=N;++i)
	{
		for(int j=1;j<=M;++j)
		{
			if(rand()/(RAND_MAX+1.0)<0.5)printf(".");
			else if(!Pd[i]&&!Px[j])printf("X"),Pd[i]=1,Px[j]=1;
		}
		printf("\n");
	}
	return 0;
}
对拍bat↓

@echo off

:loop
	data_generator.exe %random% > data.in
	std.exe < data.in > std.out
	my.exe < data.in > my.out

	fc my.out std.out

if not errorlevel 1 goto loop
pause

goto loop

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值