题目大意:
题目链接: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
×
n
n\times n
n×n的矩形中找出一个点,使得这个点到其他标记点曼哈顿距离加上标记点的权值之和最小。
思路:
10 10 10分做法:
n
≤
10
n\leq 10
n≤10
枚举这个矩阵的每一个点,再枚举每一个标记点,求出曼哈顿距离,取最小值即可。
或者从每个标记点跑
B
F
S
BFS
BFS。
时间复杂度:
O
(
n
4
)
O(n^4)
O(n4)
10分代码
30分做法:
n
≤
1000
n\leq 1000
n≤1000
假设这个图是这样子的(黄色点是标记点):
可以把第三行的所有点和第一行的特殊点的曼哈顿距离连一下看看。
会发现,它们向下的距离(蓝色部分)是一样的!
同理,下面的标记点也一样。
所以,任何一个特殊点到同一行的点的曼哈顿距离中经过的列数是一样的。
同理,所有的特殊点到同一列的点的曼哈顿距离中经过的行数是一样的。
那么我们设
h
[
i
]
h[i]
h[i]表示所有特殊点到这一行的点曼哈顿距离经过的列数,
l
[
i
]
l[i]
l[i]表示所有特殊点到这一列的点曼哈顿距离经过的行数。
那么我们就先初始化出每行每列的特殊点的个数,然后
O
(
n
2
)
O(n^2)
O(n2)枚举任意两行,求出第
j
j
j行的特殊点到第
i
i
i行的所有点的曼哈顿距离的列数(同理也要枚举任意两列)
最后可以
O
(
n
2
)
O(n^2)
O(n2)枚举所有点,蔬输出最小的那一个。
30分代码
100 100 100分做法:
O
(
n
)
O(n)
O(n)
考虑如何在30分的做法上优化。跳过了30分的朋友建议回去再可以下30分的做法。
我们可以发现,第
i
i
i行的点到所有特殊点的曼哈顿距离之和是可以通过第
i
−
1
i-1
i−1行的点得到的。如果能
O
(
1
)
O(1)
O(1)转换,那么就可以了。
首先,很容易发现:
h
[
i
]
=
h
[
i
−
1
]
+
在
第
i
−
1
行
上
面
的
特
殊
点
数
量
−
在
第
i
行
下
面
的
特
殊
点
数
量
h[i]=h[i-1]+在第i-1行上面的特殊点数量-在第i行下面的特殊点数量
h[i]=h[i−1]+在第i−1行上面的特殊点数量−在第i行下面的特殊点数量
那么如果能
O
(
1
)
O(1)
O(1)知道在任意一行上面的点的数量和下面的点的数量就可以
O
(
1
)
O(1)
O(1)转化。
所以可以求一个前缀和,
h
s
[
i
]
hs[i]
hs[i]表示在第
i
i
i行(含)之前的特殊点数量,那么就有
h
s
[
i
]
=
h
s
[
i
−
1
]
+
h
n
u
m
[
i
]
hs[i]=hs[i-1]+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(n)求出每一行的点到所有特殊点的列的曼哈顿距离之和
- O ( n ) O(n) O(n)求出每一列的点到所有特殊点的行的曼哈顿距离之和
那么就只剩下最后一个
O
(
n
2
)
O(n^2)
O(n2)的地方了:输出。
我们可以
O
(
n
)
O(n)
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;
}