CodeForces 215C 题解

题解笔记

CF215C Crosses(cross.cpp)

时间限制 \(2s\) | 内存限制 \(256M\)

题目描述:

有一个 n×m 的矩阵,我们用一个六元组 \((a,b,c,d,x_0,y_0)\) 表示一个十字形:

若一个方格 (x,y) 满足下面两个条件之一,则这个方格属于这个六元组表示的十字形

  • $ \mid x−x_0 \mid \leq a $ 且 $ \mid y−y_0 \mid \leq b $
  • $ \mid x−x_0 \mid \leq c $ 且 $ \mid y−y_0 \mid \leq d $

已知一个十字形的面积是 s,问有多少个六元组满足要求。

一个十字型如下图所示:

输入格式:

输入由一行包含三个整数 \(n , m\) 和 \(s\) ( \(1 \leq n,m \leq 500\) , \(1 \leq s \leq nm\) ) 。整数之间用空格分隔。

输出格式:

输出一个整数表示满足题目条件六元组数量。

输入输出样例:

样例1输入 样例1输出 样例2输入 样例2输出
2 2 1 4 3 4 5 4

样例解释:

在第一个样例中,满足要求的六元组是:$ ( 0 , 0 , 0 , 0 , 1 , 1 ) , ( 0 , 0 , 0 , 0 , 1 , 2 ) , ( 0 , 0 , 0 , 0 , 2 , 1 ) , ( 0 , 0 , 0 , 0 , 2 , 2 ) $ 。

在第二个样例中,满足要求的六元组是:$( 0 , 1 , 1 , 0 , 2 , 2 ) , ( 0 , 1 , 1 , 0 , 2 , 3 ) , ( 1 , 0 , 0 , 1 , 2 , 2 ) , ( 1 , 0 , 0 , 1 , 2 , 3 ) $ 。

解题思路:

下文的第一张图借用了 这篇题解 的图片( 事实上,我是看了这篇题解才 AC 了这道题 )

理解题意

下图这张是 \((2,1,0,2,5,5)\) 表示的十字,可以发现,这个十字形是由两个矩形重叠组成的,即红蓝两个矩形

中间的黑格就是 \((5,5)\),十字形的六元组。

用过这张图就大概能完美理解题意


开始正式题解

题解从这里开始

直接枚举六元组肯定是超时的

但是由于矩形也是属于十字形的,所以可以枚举矩形,根据矩形的面积可以分为如下几种情况( \(x,y\) 是矩形的长和宽 ):

  • \(xy=s\)

计算公式:

\[C_{n-x+1}^{1} \times C_{m-y+1}^{1}\\ =(n-x+1)(m-y+1) \]

但是对于每一个矩形,计算出了六元组中 \(a,b\) 的值,但是 \(c,d\) 的值还有很多种可能,即 \((i+1) \div 2 \times ( j+2)\) 种可能

  • \(xy<s\)

那对于面积不够大的情况,我们尝试在上下左右补上一部分面积,但是由于十字形是对称的,所以需要补的点应该是一个偶数,即 \(s-xy\) 应该是偶数

那怎么补?

这里并不需要在左右补,只需要在上下补即可,因为如果存在 \(x \times y\) 的矩阵使得在左右补可行的话,如下图:

左右补之后如下图示:

但为什么不能将看成一个细长的矩形(即下图的红矩形)在上下添加了一些方块(下图的蓝方块)呢?

因此只需考虑在上下添加方块就行了,由于是对称的将上下添加的部分合在一起,构成一个矩形,枚举这个矩形的长 \(k\) ,

由于 \(k\) 是长且 \(s-xy\) 是面积,所以 \(s-xy\) 一定能被 \(k\) 整除

但补后的十字是不能超过整个网格的宽的,所以 \((s-xy) \div k+x < n\)

最后一点优化

最后可以发现,所谓通过 $ \mid x−x_0 \mid \leq a $ 且 $ \mid y−y_0 \mid \leq b $ 的条件画出一个矩阵,其实是由上下左右四个矩阵构成了一个大矩阵,如下图:

四个矩形重叠的地方就是\((x_0,y_0)\) ,即中心点

而这四个矩形都是 \(a \times b\) 的大小,

所以由这张图就可以看出,矩形的长和宽一定都是奇数,因此在补方块时的矩形的长 \(k\) 一定也是奇数

参考代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int n,m,s;
ll ans;

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1; i<=n; i+=2)
    {
        for(int j=1; j<=m; j+=2)
        {
            int lst=s-i*j;
            if(lst==0) ans+=(2ll*((i-1)/2+1)*((j-1)/2+1)-1)*(n-i+1)*(m-j+1);
            else if(lst>0&&!(lst&1))
            {
                for(int k=1; k<j; k+=2)
                {
                    if(!(lst%k)&&i+lst/k<=n) ans+=2ll*(n-i-lst/k+1)*(m-j+1);
                }
            }
        }
    }
    printf("%lld",ans);
    return 0;
}
posted @ 2021-12-18 10:23  yhang323  阅读(52)  评论(0)    收藏  举报