链接
http://codeforces.com/contest/1004/problem/D
新发现
这个曼哈顿距离表格以前没研究过啊,这东西还是挺神奇的。由于它具有高度对称性,因此会具有很多几何性质,因为它只涉及到非负整数,所以它和数论还有些关系。
为了方便叙述我在
建立直角坐标系,那么
轴和
轴都是对称轴,
是对称中心,相同的数字呈现闭合的菱形分布。
在每一个象限内,直线
或
(k是任意非零整数)上的数字都呈现公差为
的等差数列,所有斜率为
的直线上的点都呈现公差为
的等差数列
到任何一个点曼哈顿距离的最大或最小值都在四个角之一,边界上离某个点曼哈顿距离最小的点都是从该点向边界做垂线交得的点
出题人的意思
由于这个东西具有对称性,每一种解都可以通过翻转将它变化出最多四种解。
我们仅考虑
在靠近左边界或者上边界的解。
举个例子,题目中给出了这种情形:
那么我们可以等价为:
设
分别为到
最近、最远的点,那么
到
的距离设为
,
到
的距离设为
显然
分别是给出的数字中的最大和最小值
列式
就是给出的数字中最小的出现次数不为本身值的 倍的数字,这个很好理解,因为我们求的是 在左上的解,当曼哈顿距离相同数字构成的菱形在向外一层一层扩展时,首先触碰到的是左边界或者上边界,在这之前每个数字都比前一个数字的出现次数多 ,出现次构成了通项为 的等差数列。在刚好触碰边界的数字的下一个数字,就不满足这个通项了,而观察表格就发现这个数字的值恰好等于 到边界的最小距离。
那么你求出这个数字之后,它应该作为 的值或者作为 的值,但是我为什么说它就是 的值?因为我可以通过一次 旋转对这两种情况做出等价变形。
现在 已知,由上述方程组可以得到
现在就差 不知道了,
显然
所以 都是 的约数,这个肯定可以 枚举
最后的复杂度取决于 的约数个数
复杂度为
改进算法的稳定性
其实很多解都是不合法的,当找到一组
时,代回
两式检验,理论上二元二次方程有四组根,所以复杂度是
的
这样就不会被那些约数超多的数字卡掉了
代码
#include <cstdio>
#include <algorithm>
#define maxn 1000010
using namespace std;
int t, n, m, x, y, cnt[maxn], c[maxn], a, b;
int read(int x=0)
{
char c;
for(c=getchar();c<48 or c> 57;c=getchar());
for(;c>=48 and c<=57;c=getchar())x=(x<<1)+(x<<3)+c-48;
return x;
}
int main()
{
int i, j, k, dist;
t=read();
for(i=1;i<=t;i++)c[read()]++;
for(i=1;i<maxn;i++)if(c[i])b=max(b,i);
for(i=1;i<maxn;i++)if(c[i]!=i<<2){x=i;break;}
for(n=1;n<=t;n++)
if(t%n==0)
{
m=t/n;
y=n+m-x-b;
if(abs(n-x)+abs(m-y)!=b)continue;
for(i=0;i<=n+m;i++)cnt[i]=0;
for(i=1;i<=n;i++)for(j=1;j<=m;j++)cnt[abs(x-i)+abs(y-j)]++;
for(i=0;i<=n+m;i++)if(cnt[i]!=c[i])break;
if(i>n+m)
{
printf("%d %d\n%d %d",n,m,x,y);
return 0;
}
}
printf("-1");
return 0;
}