【JZOJ5775】农夫约的假期【模拟】

题目大意:

题目链接:https://jzoj.net/senior/#main/show/5775
题目图片:
https://www.z4a.net/images/2018/09/23/1.png
https://www.z4a.net/images/2018/09/23/2.md.png
https://www.z4a.net/images/2018/09/23/3.png
https://www.z4a.net/images/2018/09/23/4.png
n×nn\times n的矩形中找出一个点,使得这个点到其他标记点曼哈顿距离加上标记点的权值之和最小。


思路:

1010分做法:

n10n\leq 10
枚举这个矩阵的每一个点,再枚举每一个标记点,求出曼哈顿距离,取最小值即可。
或者从每个标记点跑BFSBFS
时间复杂度:O(n4)O(n^4)
10分代码


30分做法:

n1000n\leq 1000
假设这个图是这样子的(黄色点是标记点):

在这里插入图片描述

可以把第三行的所有点和第一行的特殊点的曼哈顿距离连一下看看。
在这里插入图片描述

会发现,它们向下的距离(蓝色部分)是一样的!
在这里插入图片描述

同理,下面的标记点也一样。
在这里插入图片描述
所以,任何一个特殊点到同一行的点的曼哈顿距离中经过的列数是一样的。
同理,所有的特殊点到同一列的点的曼哈顿距离中经过的行数是一样的。
那么我们设h[i]h[i]表示所有特殊点到这一行的点曼哈顿距离经过的列数,l[i]l[i]表示所有特殊点到这一列的点曼哈顿距离经过的行数。
那么我们就先初始化出每行每列的特殊点的个数,然后O(n2)O(n^2)枚举任意两行,求出第jj行的特殊点到第ii行的所有点的曼哈顿距离的列数(同理也要枚举任意两列)
最后可以O(n2)O(n^2)枚举所有点,蔬输出最小的那一个。
30分代码


100100分做法:

O(n)O(n)
考虑如何在30分的做法上优化。跳过了30分的朋友建议回去再可以下30分的做法。
我们可以发现,第ii行的点到所有特殊点的曼哈顿距离之和是可以通过第i1i-1行的点得到的。如果能O(1)O(1)转换,那么就可以了。
首先,很容易发现:
h[i]=h[i1]+i1ih[i]=h[i-1]+在第i-1行上面的特殊点数量-在第i行下面的特殊点数量
那么如果能O(1)O(1)知道在任意一行上面的点的数量和下面的点的数量就可以O(1)O(1)转化。
所以可以求一个前缀和,hs[i]hs[i]表示在第ii行(含)之前的特殊点数量,那么就有
hs[i]=hs[i1]+hnum[i]hs[i]=hs[i-1]+hnum[i]
知道了上面的特殊点数量,那么下面的特殊点数量就一目了然了。
那么就可以先O(n)O(n)求出第一行的点到所有特殊点的曼哈顿距离,然后就可以推出每一行到特殊点的距离。
列也同理,可以O(n)O(n)求出。
那么我们就做到了:

  • O(n)O(n)求出每一行的点到所有特殊点的列的曼哈顿距离之和
  • O(n)O(n)求出每一列的点到所有特殊点的行的曼哈顿距离之和

那么就只剩下最后一个O(n2)O(n^2)的地方了:输出。
我们可以O(n)O(n)知道所有行的最小列曼哈顿距离,又可以O(n)O(n)知道所有列的最小曼哈顿距离,那么这两行的交点就是到所有标记点曼哈顿距离最短的点!
在这里插入图片描述


代码:

#include <cstdio>
#include <cmath>
#include <iostream>
#define ll long long
#define N 110000
using namespace std;

ll n,m,x,y,q,z,h[N],l[N],hnum[N],lnum[N],hs[N],ls[N],minn,sum;

int main()
{
	scanf("%lld%lld%lld",&n,&m,&z);
	for (ll i=1;i<=m;i++)
	{
		scanf("%lld%lld%lld",&x,&y,&q);
		hnum[x]++;
		lnum[y]++;  //每行每列的标记点数量
		sum+=q;
	}
	for (ll i=1;i<=n;i++)
	 hs[i]=hs[i-1]+hnum[i];
	for (ll i=1;i<=n;i++)
	 ls[i]=ls[i-1]+lnum[i];  //前缀和
	for (ll i=1;i<=n;i++)
	 h[1]+=(hnum[i]*abs(i-1)*z);
	for (ll i=1;i<=n;i++)
	 l[1]+=(lnum[i]*abs(i-1)*z);  //求出第一行第一列
	for (ll i=2;i<=n;i++)
	 h[i]=h[i-1]+hs[i-1]*z-(m-hs[i-1])*z;
	for (ll i=2;i<=n;i++)
	 l[i]=l[i-1]+ls[i-1]*z-(m-ls[i-1])*z;  //往后转移
	minn=1e16;
	for (ll i=1;i<=n;i++)
	 if (h[i]<minn)   //找最小的行
	 {
	 	minn=h[i];
	 	x=i;
	 }
	minn=1e16;
	for (ll i=1;i<=n;i++)
	 if (l[i]<minn)  //找最小的列
	 {
	 	minn=l[i];
	 	y=i;
	 }
	cout<<h[x]+l[y]+sum<<"\n"<<x<<" "<<y;
	return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998562.html