1 条题解
-
0
自动搬运
来自洛谷,原作者为

bianshiyang
乌云过后仍会是漫天星辰搬运于
2025-08-24 22:51:37,当前版本为作者最后更新于2023-10-28 21:38:27,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
本蒟蒻使用数学公式求解本题,所涉及的数学公式有些繁杂,但解释十分详尽,请各位大佬耐心读完谢谢。
题目简意:
有一个 的数表,其中 。现在给出 次询问,每次询问给出 和 ,需要你求出如何分割数表成两部分 和 ,使得 最小,并输出切割方案。
分析:
暴力是肯定无法 AC,因为 ,,。这不仅使得 遍历都会超时,且二维树状数组求前缀和也会 MLE,所以容易想到用数学求解公式, 算出答案。
首先我们要处理 是什么意思,
如果你尝试模拟过的话,就知道它相当于是从 开始然后顺序填充整个数表直到 ,例如当 , 时,数表应该形如下图:1 2 3 4 5 6 7 8 9 10 11 12 那么不难发现无论我们怎么分割这个数表,所得到的 $\sum x+\sum y= \displaystyle\sum_{i=1}^n\displaystyle\sum_{j=1}^m a_{i,j}$,而 $\displaystyle\sum_{i=1}^n\displaystyle\sum_{j=1}^m a_{ij}$ 为一定值,不妨将这个定值设为 ,且为了方便表示,将 设为 , 设为 。
再来看 ,变形一下,,如果我们将它写成以下形式:
$\begin{dcases}fx &\text{}fx\ge fy \\N-fx &\text{} fx<fy\end{dcases}$
此时进行分类讨论: 若 ,
- 则有
- 即
- 故此时
若 ,
- 则有
- 即
- 故此时
而等号在哪一边取等都是可以的,所以 。那么我们只要找到一种分割方案使得 ,那么此方案一定是最优的。而只有当 时,才可能取等,所以接下来只需要找到 和 的表达式即可。
的表达式很容易求,$N=\displaystyle\sum_{i=1}^{m n}i=\frac{mn\cdot (mn+1)}{2}$,那么 。
先考虑横着分割数表假设确定的 为 ,那么相当于在第 行和第 行将数表分开,因为 和 地位等价,不妨假设 为第一行到第 行求和,也就是从 加到 ,自然 $fx=\displaystyle\sum_{i=1}^{m\cdot(k-1)}i=\frac{m\cdot (k-1)\cdot [m(k-1)+1]}{2}$。
此时令 $\frac{m\cdot (k-1)\cdot [m(k-1)+1]}{2}=\frac{N}{2}=\frac{mn\cdot (mn+1)}{4}$,
可化简得 ,
这便是关于 的一个一元二次方程,如果我们能够得到 的整数解并且 ,那么最小值就是 ,否则我们找到 和 ,并将它们分别带入 看 谁更小,那么谁就是结果。容易证明 、、一定有一个属于 的。
然后考虑竖着分割数表,此时求解 是有一些难度的,根据题目所定义 ,此时若仍假设确定的 为 ,那么相当于在第 列和第 列将数表分开,所以
$fx=\displaystyle\sum_{i=1}^n\displaystyle\sum_{j=1}^{k-1}a_{i,j}=\displaystyle\sum_{i=1}^n\displaystyle\sum_{j=1}^{k-1}m(i-1)+j$
$=\displaystyle\sum_{i=1}^n[m(k-1)(i-1)+\displaystyle\sum_{j=1}^{k-1}j]$
$=m(k-1)\displaystyle\sum_{i=1}^n(i-1)+\displaystyle\sum_{i=1}^n\frac{(k-1)(k-1+1)}{2}$
$=m(k-1)\displaystyle\sum_{i=1}^ni-m(k-1)\displaystyle\sum_{i=1}^n1+\frac{nk(k-1)}{2}$
此时令 $\frac{mn(n+1)(k-1)}{2}-nm(k-1)+\frac{nk(k-1)}{2}=\frac{N}{2}=\frac{mn\cdot(mn+1)}{4}$,
可化简得 。
这仍是关于 的一个一元二次方程,如果我们能够得到 的整数解并且 ,那么最小值就是 ,否则我们找到 和 ,并将它们分别带入 看 谁更小,那么谁就是结果。
至此我们的分析已经结束了,直接上代码!
代码实现
#include <bits/stdc++.h> #define int long long//不开long long见祖宗 #define inf 9e18 using namespace std; int t, n, m; int ans1, ans2;//ans1表示是横着切还是竖着切,ans2表示切在什么位置 double bans, aa, bb, k;//bans表示当前最小的max double work() {//竖着切 double fa = (double)2; double fb = (double)2 * (m * n - m - 1); double fc = (double) -2 * m * n + m - m * m * n; double der = sqrt(fb * fb - 4 * fa * fc); double aa1 = (-fb + der) / (2 * fa);//容易证明小的根是负数,所以返回大的根 return aa1; } double deal(int x) {//求当x=k时竖着切的fx double f1 = (m * n * (n + 1) * (x - 1)) / 2; double f2 = m * n * (x - 1); double f3 = (n * x * (x - 1)) / 2; double res = f1 - f2 + f3; double tot = (m * n * (m * n + 1)) / 2; return max(res, tot - res); } double work2() {//横着切 double fa = (double)2 * m; double fb = (double)(2 - 4 * m); double fc = (double)(2 * m - 2 - m * n * n - n); double der = sqrt(fb * fb - 4 * fa * fc); double aa1 = (-fb + der) / (2 * fa); return aa1; } double deal2(int x) {//求当x=k时横着切的fx double res = (m * (x - 1) * (m * (x - 1) + 1)) / 2; double tot = (m * n * (m * n + 1)) / 2; return max(res, tot - res); } signed main() { scanf("%lld", &t); while (t--) { scanf("%lld%lld", &n, &m); ans1 = ans2 = 0; aa = bb = bans = inf;//初始化以防万一 k = work();//先求根,这里要先求竖着切的,因为“如果有多解,请输出竖切的一种” if (k == floor(k)) {//k为整数 ans1 = 0; ans2 = k; bans = deal(k); } else { int k1 = floor(k);//下取整 int k2 = k1 + 1;//上取整 ans1 = 0; if (k1 > 1 && k1 <= m) aa = deal(k1); if (k2 > 1 && k2 <= m) bb = deal(k2); ans2 = aa <= bb ? k1 : k2; bans = aa <= bb ? aa : bb; } k = work2();//求横着切的 if (k == floor(k)) { int lin = deal2(k); if (lin < bans) {//只有小于竖着切的情况才更新 ans1 = 1; ans2 = k; bans = lin; } } else { int k1 = floor(k); int k2 = k1 + 1; if (k1 > 1 && k1 <= n) aa = deal2(k1); if (k2 > 1 && k2 <= n) bb = deal2(k2); if (aa < bans) { ans1 = 1; ans2 = k1; bans = aa; } if (bb < bans) { ans1 = 1; ans2 = k2; bans = bb; } } if (ans1 == 0)//ans1为0就是竖着切 printf("V "); else//否则横着切 printf("H "); printf("%lld\n", ans2); } return 0; }感谢各位大佬耐着性子看到这里,
如果觉得还不错的话点个赞再走吧。
- 1
信息
- ID
- 8697
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 3
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者