迷失的国王
题目链接:迷失的国王
题目描述:
在一个古老的王国里,有一位慈祥的国王。某天,国王突然迷失在了自己的宫殿中。宫殿是一个 n×m 的棋盘,国王想要找到两个相同的宝藏,这样他才能找到回家的路。
国王知道两个相同的宝藏在宫殿上的位置,并且他们之间的曼哈顿距离为 k。国王希望知道有多少种放置两个宝藏的方式,使得它们的曼哈顿距离恰好为 k。
国王请求你作为王国最聪明的智者,设计一个算法来解决这个问题。
其中在平面上,坐标 (x1,y1) 的点 P1 与坐标 (x2,y2) 的点 P2 的曼哈顿距离为:
d(x,y)=|x1−x2|+|y1−y2|
其中 |x| 表示绝对值符号,当 x<0 时,|x|=−x,否则 |x|=x。
图中左下角的点 P1 坐标为 (0,0) ,右上角 P2 坐标为 (6,6),那么这两点之间的曼哈顿距离为 12。
图中红、蓝与黄线的长度与 P1 和 P2 的曼哈顿距离相等。
输入格式:
三个正整数 n,m,k (1≤n,m,k≤106)
输出格式:
一个整数表示方案数。
数据范围:
-
对于 20% 的数据有:1≤n,m,k≤100。
-
对于 40% 的数据有:1≤n,m,k≤500。
-
对于 60% 的数据有:1≤n,m,k≤5000。
-
对于 100% 的数据有 1≤n,m,k≤106。
样例输入:
样例1: 8 8 14 样例2: 8 8 3 样例3: 4 3 3
样例输出:
样例1: 2 样例2: 248 样例3: 17
提示:
样例一有两种方案,或者放在左上角和右下角,或者放在左下角和右上角。
时间限制: 1000ms
空间限制: 256MB
初见还是有点困难的,由于数据很大,所以进行爆搜是无法通过全部样例的,即便用一些巧妙的方法进行搜索也只能够拿到60分,因此我们不妨分析一下我们的问题。
首先我们应该观察一下每个点各自曼哈顿距离为k的点我们可以得知当一个点处于一个大的图形中间时与它曼哈顿距离为k的点分布为一个棱形,列如:
0 0 # 0 0
0 # 0 # 0
# 0 * 0 #
0 # 0 # 0
0 0 # 0 0
(我们将星号定为我们的起始点,当k=2时井号为与起始点曼哈顿距离为k的点)
k=3时
0 0 0 # 0 0 0
0 0 # 0 # 0 0
0 # 0 0 0 # 0
# 0 0 * 0 0 #
0 # 0 0 0 # 0
0 0 # 0 # 0 0
0 0 0 # 0 0 0
假设我们遍历每一个点,统计的过程中我们可能会将同行或者同列的点重复统计,为了解决这个问题我们可以直接通过算式来计算同行、同列曼哈顿距离满足条件的点,公式为
max(0ll,n*(m-k));
max(0ll,m*(n-k));
所以我们可以轻易的得到同行同列满足条件的点一共有几对,然后我们来思考一下如何统计距离中心点四个斜方向的满足条件的点的数量
0 0 0 0 0 0 0
0 0 # 0 # 0 0
0 # 0 0 0 # 0
0 0 0 * 0 0 0
0 # 0 0 0 # 0
0 0 # 0 # 0 0
0 0 0 0 0 0 0
我们思考一下,每一个中间的点都可能是它前几行的某个点的满足条件点,如我们上方矩阵,以点A(2,3)为起始点,我们的‘*’所在位置将会是一个满足条件的点,所以为了防止重复统计,我们可以只向下寻找符合条件的点,那我们每个点与它符合条件的点的图形可以简化为
0 0 0 * 0 0 0
0 # 0 0 0 # 0
0 0 # 0 # 0 0
0 0 0 0 0 0 0
(去掉了上面的点)
这个时候就能发现在下方剩余空间相同的情况同一行的点向左的对应点数量和以一条平分矩阵的竖线镜像之后的位置向右边的点的数量是一样的,例如:
0 0 * 0 0 0 0
# 0 0 0 0 0 0
0 # 0 0 0 0 0
0 0 0 0 0 0 0
(星号点向左寻找满足条件的点的情况)
0 0 0 0 * 0 0
0 0 0 0 0 0 #
0 0 0 0 0 # 0
0 0 0 0 0 0 0
(镜像之后向右寻找的情况)
我们可以分析出我们只需要挑左右一个方向统计数量,然后再将统计的数字乘以2就可以得到我们一整行到下方的符合条件的点数量有几个
那有没有在不跑双for的情况下快速获得一行的符合条件的点的总数的方式呢?
有的兄弟,有的!
当‘*’的位置没有超过一定范围时,我们每将‘*’向右移动一格,*左边的符合条件的点的数量就会+1
* 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
↓
0 * 0 0 0 0 0
0 0 0 0 0 0 0
# 0 0 0 0 0 0
0 0 0 0 0 0 0
↓
0 0 * 0 0 0 0
# 0 0 0 0 0 0
0 # 0 0 0 0 0
0 0 0 0 0 0 0
而分析这个点的最大数量可知
int u = min(k-1,n-x);
if(u==k)u--;
此时的u就是当前行中一个点向左下角寻找可以得到的最多的点的数量,但是具体能不能达到这个数字就得看我当前行够不够长了,我们可以得知向左下角寻找符合条件的点为1的位置是
int l = k - u+1;
以这个点为这一行的起点,往后第u-1个点可以取得我们的最大值点,再向后直到行末每个点向右下角都可以取到u个点满足条件,所以我们最终的公式可以列为
int cnt = 0 ;
int u = min(k-1,n-x);
if(u==k)u--;
int l = k - u+1;
int r = min(m,l+u-1);
if(l>r)return 0;
cnt+=count(r-l+1);
int res = (m-r)*(r-l+1);
cnt+=res ;
cnt*=2;
最终代码为:
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define int long long
#define PII pair<int,int>
#define fi first
#define pb push_back
#define sec second
#define endl '\n'
#define INF 0x3f3f3f3f
const int N=2e5+10;
int n,m,k,t;
int sum = 0 ;
int count(int x ){
return (1+x)*x/2;
}
int get(int x){
int cnt = 0 ;
int u = min(k-1,n-x);
if(u==k)u--;
int l = k - u+1;
int r = min(m,l+u-1);
if(l>r)return 0;
cnt+=count(r-l+1);
int res = (m-r)*(r-l+1);
cnt+=res ;
cnt*=2;
// cout << x << ' ' << l << ' ' << r << " " << res << endl;
return cnt ;
}
signed main(){
cin >> n >> m >> k ;
for(int i=1;i<=n;i++){
sum+=get(i);
// for(int j=1;j<=m;j++){
if(j!=1)cout << " ";
cout << get(i,j);
// sum+=get(i,j);
// }
// cout << endl;
}
sum+=max(0ll,n*(m-k));
sum+=max(0ll,m*(n-k));
cout << sum << endl;
return 0;
}
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 2 1 * 1 2 0 0
//0 0 0 1 0 0 0 0
//0 0 0 2 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 * 0 0 0 0 0 0
//0 0 0 0 0 0 0 0
//0 0 0 0 0 0 0 0