HDU-5976-Detachment
这道题是二分 + 乘法逆元~
我今天在看基础数论的时候,发现好多种求逆元不同的方法,原先我只知道用费马小定理的方法,今天才感慨。。。(世界如此之大)
这道题二分我是手写的代码。。。emm
什么是乘法逆元?参考博客链接:
https://blog.csdn.net/qq_44624316/article/details/105677150
题目大意:给你一个数n,让你分解n,使得分解出来的数之和为n,分解出来的数各不相同并且尽可能使得这些分解出来得数乘积尽可能的大。
如果给我们一个数,要求分解,没有要求数字各不相同的情况下,我们就尽可能地分解成每个数都相等,这样可以使得乘积尽可能地大(因为我们小学的时候就知道周长相等的情况下正方形的面积最大嘛)
对于这种题目,有一个比较明显的结论:必然是取得一段连续的自然数使得乘积最大。
而且只有两种可能:
-
case1: 最大的乘积 = 2 * 3 * …* (r - 1) * (r + 1) *… * (k-1) * k
这种是以2开头 -
case2: 最大的乘积 = 3 * 4 * … * (r -1) * (r + 1) * … * (k - 1) * k
这种是以3开头 -
关于使得乘积最大的必然是一段连续的自然数的证明如下:
对于任意所给的整数n不可能恰好凑出连续的自然数之和,可能会多出∆x。这个∆x一定满足下面的不等式:
为什么会小于等于k呢?
运用反证法我们可以得到:
如果∆x大于k的话,我们就可以直接凑出k+1了,加在后面即可。
我们把多出来的∆x从后往前均摊给每一个凑出来的数字。
关于构造出来的一段连续自然数有以下性质:
性质1:a_(i+1)-a_i≤2
性质2:最多有一个i满足a_(i+1)-a_i=2
性质3:1<a_1 (第一个数)≤3
于是我们可以得到解的结构为:
所以对于这道题:
我们可以先预处理出前缀和sum[],前缀积mul[],以及运用递推的方法线性处理出逆元inv[];
如果输入给出的n值小于5,不需要分解,直接输出即可。
其余情况,我们二分查找出sum[]最接近的值(我这里没有用lower_bound()是因为我用的ll类型的数组,编译报错,这里如果要用lower_bound()可以通过C++中的模板template重写)
找出来以后我们计算一下∆x为多少,保存在res中。
根据上面说的:我们把res+2与二分出来的l比较判断大小,如果大于就对应上面的第二种情况,3开头的。乘了逆元之后后面直接再乘上res+2即可
如果小于就对应上面的第一种情况,2开头的。乘了逆元之后后面再乘上l+1;
好啦我说完啦~(因为markdown上面编辑公式有点用不惯,,我就直接用word弄好贴图过来的)
代码部分:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const ll mod = 1e9 + 7;
ll sum[N], mul[N], inv[N];
int n;
ll ans;
ll l, r;
ll mid;
void init()
{
sum[1] = 0;
mul[1] = 1;
inv[1] = 1;
for (int i = 2; i <= N; i++)
{
sum[i] = sum[i - 1] + i;
mul[i] = (mul[i - 1] % mod) * (i % mod) % mod;
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
}
int main()
{
int t;
cin >> t;
init();
while (t--)
{
scanf ("%d", &n);
if (n < 5)
{
cout << n << endl;
continue;
}
l = 2;
r = N;
mid = (l + r) >> 1;
while (l + 1 < r)
{
if (sum[mid] > n)
{
r = mid;
mid = (l + r) >> 1;
}
else
{
l = mid;
mid = (l + r) >> 1;
}
}
ll res = n - sum[l];
if (2 + res > l)
{
ans = (mul[l] * inv[2]) % mod * (res + 2) % mod;
}
else
{
ans = mul[l] * inv[l - res + 1] % mod * (l + 1) % mod;
}
cout << ans << endl;
}
return 0;
}