P9762 [ROIR 2021 Day 1] 分割数表 题解
bianshiyang · · 题解
题目传送门
本蒟蒻使用数学公式求解本题,所涉及的数学公式有些繁杂,但解释十分详尽,请各位大佬耐心读完谢谢。
题目简意:
有一个
分析:
暴力是肯定无法 AC,因为
首先我们要处理 如果你尝试模拟过的话,就知道它相当于是从
| 1 | 2 | 3 | 4 |
|---|---|---|---|
| 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 |
那么不难发现无论我们怎么分割这个数表,所得到的
再来看
此时进行分类讨论:
若
- 则有
fx\ge N-fy - 即
fx\ge\frac{N}{2} - 故此时
\max{\{fx,fy}\}=fx\ge\frac{N}{2}
若
- 则有
fx< N-fy - 即
fx<\frac{N}{2} - 故此时
\max{\{fx,fy}\}=N-fx>\frac{N}{2}
而等号在哪一边取等都是可以的,所以
此时令
可化简得
这仍是关于
至此我们的分析已经结束了,直接上代码!
代码实现
#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;
}
感谢各位大佬耐着性子看到这里,如果觉得还不错的话点个赞再走吧。