题目
测试链接:咕咕咕
题目背景
寒蝉凄切,对长亭晚,骤雨初歇。都门帐饮无绪,留恋处,兰舟催发。执手相看泪眼,竟无语凝噎。念去去,千里烟波,暮霭沉沉楚天阔。
多情自古伤离别,更那堪,冷落清秋节!今宵酒醒何处?杨柳岸,晓风残月。 此去经年,应是良辰好景虚设。便纵有千种风情,更与何人说! ——宋·柳永《雨霖铃》字虽好,只是柳永伤感。执手相看泪眼,竟无语凝噎。太不合此情此景。 ——爱新觉罗氏胤禛
题目描述
西施与范蠡泛舟而去……不对,场景不对,咳咳。在甄嬛前往蓬莱洲之前,她与皇上在碧桐书院告别。为了这可能会长达数月的离别,两个人都似乎有很多话要对对方说,却都无语凝噎。
这时,皇上突然发话:「嬛嬛啊,既然你我都说不出话来,那这时间也不好打发,我们来数三角形吧。」为了满足皇上突发而来的童趣,甄嬛欣然陪同了。可这……
纸上是一张 (n imes m) 的格子方阵,即有 ((n+1) imes (m+1)) 个格点。每个格子都是边长为 (1) 的正方形。而他们要数的,则是任取 (3) 个格点作为三角形的顶点所形成的 直角三角形 且该三角形面积为 (dfrac{s}{2}) 的个数。甄嬛数得头都晕了,她现在只想知道满足条件的三角形个数 (mod{10^9+7})
输入格式
第一行 (3) 个正整数 (n)、(m)、(s),意义如题。
输出格式
仅一个整数,为满足条件的三角形个数 (mod{10^9+7})。
评测限制
源程序文件名为 wordless.*
,其中 *
为所用语言的源程序拓展名。
从 wordless.in
读入,输出至 wordless.out
中。
评测时间限制 (1000 extrm{ms}),空间限制 (128 extrm{MiB})。
数据范围
- 对于 (10\%) 的数据,(nle 10);
- 对于另外 (40\%) 的数据,(s) 为质数;
- 对于 (100\%) 的数据,(1le n,m,sle 10^8)。
分析
题目大意是说,给你一个 (n imes m) 的点阵,求出所有由格点作为顶点的直角三角形总数。
本题的难点在于,平面格点上不一定只有直角边平行于坐标轴的直角三角形,还有「斜着」的。如何统计这类三角形是重中之重。
(10 exttt{pts})
枚举所有组合,检查是否合法即可。
(50 exttt{pts})
这种情况比较特别。其实可以分成两类
平着的三角形
显然,即边长只能是一个为 (1),另一个为 (s)。虽然这并没有什么大作用。
斜着的三角形
这就会比一般情况简单很多,因为只有可能是边长为 (sqrt{s}) 的 等腰直角三角形,易证。
所以,我们只要考虑如何才能统计等腰直角三角形的数量即可。
仿照上面,我们惊奇地发现了两个全等三角形:
我们利用这一点,把这个等腰直角三角形放进一个长方形内,也有 (4) 种方案。
这样,这档分就可以了。
(100 exttt{pts})
其实,这个图就是我们完成的关键。我们很容易证明,一组等腰三角形必然对应着一个 唯一的 边平行于坐标轴的最小外界长方形。
这个「一组」可能是 (4) 个,也有可能是 (2) 个,取决于这个等腰三角形的方向。如果两条直角边正好与坐标轴构成了 (45^{circ}) 还等腰就只有俩。
而这个长方形的大小,就需要外面那对 相似三角形 来确定。因为并不一定等腰,所以我们并不能直接得到相似比。
我们设(上面那个图)
则易得
不妨设 (kle 1),我们就可以很轻松地算出长方形的长和宽。
同时,我们只需要枚举 (a) 和 (b) 就可以求出所有的三角形。
这就是满分做法的核心部件。
Code
虽然挺烦的,但是锻炼一下码力也是不错的。
#include <cstdio>
#include <cctype>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
inline bool is_int(double b) { return (b - int(b)) < 1e-5; }
inline int read()
{
int ch = getchar(), t = 1, n = 0;
while (isspace(ch)) { ch = getchar(); }
if (ch == '-') { t = -1, ch = getchar(); }
while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
return n * t;
}
int main()
{
ll ans = 0;
int n, m, s;
double k;
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i * i <= s; i++)
{
if (!(s % i))
{
if (i <= n && (s / i) <= m)
ans = (ans + (ll(n - i + 1) * ll(m - s / i + 1) % mod * 4)) % mod;
if (i * i != s && i <= m && (s / i) <= n)
ans = (ans + (ll(n - s / i + 1) * ll(m - i + 1) % mod * 4)) % mod;
}
for (int j = i; i * i + j * j <= s; j++)
{
k = double(s) / (i * i + j * j);
if (is_int(k * i) && is_int(k * j))
{
if (j + k * i <= n && k * j <= m)
ans = (ans + (ll(n - j - k * i + 1) * ll(m - k * j + 1)) % mod * (4 - 2 * (i == j))) % mod;
if (k * j <= n && j + k * i <= m)
ans = (ans + (ll(m - j - k * i + 1) * ll(n - k * j + 1)) % mod * (4 - 2 * (i == j))) % mod;
}
}
}
printf("%lld
", ans);
return 0;
}
后记
一道有意思的模拟题,虽然掺杂了一点数学知识。
做题时,这种题一般很无聊,但做上几题其实也没有什么坏处。