Codeforces Round #495 (Div. 2) 1004D - Sonya and Matrix

链接

http://codeforces.com/contest/1004/problem/D

新发现

这个曼哈顿距离表格以前没研究过啊,这东西还是挺神奇的。由于它具有高度对称性,因此会具有很多几何性质,因为它只涉及到非负整数,所以它和数论还有些关系。
这里写图片描述
为了方便叙述我在 (0,0) ( 0 , 0 ) 建立直角坐标系,那么 x x 轴和y轴都是对称轴, (0,0) ( 0 , 0 ) 是对称中心,相同的数字呈现闭合的菱形分布。
在每一个象限内,直线 x=k x = k y=k y = k (k是任意非零整数)上的数字都呈现公差为 1 1 的等差数列,所有斜率为±1的直线上的点都呈现公差为 2 2 的等差数列
到任何一个点曼哈顿距离的最大或最小值都在四个角之一,边界上离某个点曼哈顿距离最小的点都是从该点向边界做垂线交得的点

出题人的意思

由于这个东西具有对称性,每一种解都可以通过翻转将它变化出最多四种解。
我们仅考虑(x,y)在靠近左边界或者上边界的解。
举个例子,题目中给出了这种情形:
这里写图片描述
那么我们可以等价为:
这里写图片描述
a,b a , b 分别为到 0,0 0 , 0 最近、最远的点,那么 (1,1) ( 1 , 1 ) (x,y) ( x , y ) 的距离设为 a a (n,m) (x,y) ( x , y ) 的距离设为 b b
显然a,b分别是给出的数字中的最大和最小值
列式

{a=x1+y1b=nx+my(1)(2) { a = x − 1 + y − 1 ( 1 ) b = n − x + m − y ( 2 )

x x 就是给出的数字中最小的出现次数不为本身值的4倍的数字,这个很好理解,因为我们求的是 (x,y) ( x , y ) 在左上的解,当曼哈顿距离相同数字构成的菱形在向外一层一层扩展时,首先触碰到的是左边界或者上边界,在这之前每个数字都比前一个数字的出现次数多 4 4 ,出现次构成了通项为4n的等差数列。在刚好触碰边界的数字的下一个数字,就不满足这个通项了,而观察表格就发现这个数字的值恰好等于 0 0 到边界的最小距离。
那么你求出这个数字之后,它应该作为x的值或者作为 y y 的值,但是我为什么说它就是x的值?因为我可以通过一次 90 90 ∘ 旋转对这两种情况做出等价变形。
现在 x,a,b x , a , b 已知,由上述方程组可以得到 y=n+mbx y = n + m − b − x
现在就差 n,m n , m 不知道了,
显然 nm=t n m = t
所以 n,m n , m 都是 t t 的约数,这个肯定可以Θ(t)枚举
最后的复杂度取决于 t t 的约数个数
复杂度为O(σ0(t)t)

改进算法的稳定性

其实很多解都是不合法的,当找到一组 (n,m) ( n , m ) 时,代回 (1),(2) ( 1 ) , ( 2 ) 两式检验,理论上二元二次方程有四组根,所以复杂度是 Θ(t) Θ ( t )
这样就不会被那些约数超多的数字卡掉了

代码

#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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值