逆元+前缀和+前缀乘+二分+贪心

http://acm.hdu.edu.cn/showproblem.php?pid=5976

题意:

前言:一维 即一条线段的长度,二维 即x*y,三维 即x*y*z, 四维 即x*y*z*h, 五维。。。n维

然后题目给你一个数x   把这个数拆成x= ai + aj +...+ak (两两不相等)

然后输出这些数累乘的结果 再模一个数  要求结果最大

结论1:贪心策略 --》要想乘积最大:连续乘最好 2*3*4*5.。。。
        我们要乘积最大,那么我们把n尽可能的拆分,如果题目不要求ai≠aj,那么我们将x拆分成什么最好呢?
        显然拆成2和3(2为偶数,3为奇数,2x+3y可以表示大于1的所有正整数)是最好的,顺便提下3最多的时候最优,
        为什么不是2,将,6拆成3个2乘积为8,而将6拆成3乘积为9,(不会证)

注意:x最大10的九次方 测试发现最好那个数是45000      累乘结果会爆  所以只能保存取模结果

由于后面的结论推出 要除一些数  所以用到逆元

公式:  inv[i] = (mod - mod / i) * inv[mod % i] % mod;    //公式 递推求

扫描二维码关注公众号,回复: 3547667 查看本文章

或者快速幂  

二分查找:找到第一个大于等于 利用负数来看比较好   lower_bound

如果找到小于等于 那个位置  接下来要补数  反而不好理解

收获:手动二分 如果那个数不存在的话 会找小于等于这个数的位置   如果结果加1 也做不到 lower那样大于等于 因为等于的情况就错了。所以以后尽量别手动,手动的话也最好是找一个确定存在的数。这样才不易错

#include<iostream>
#include<cstring>
#include<cmath>
#include<string>
#include<map>
#include<vector>
#include<set>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<functional>

using namespace std;
#define inf 0x3f3f3f3f
#define pi acos(-1.0)
#define LL long long
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
//#define mp make_pair
#define pb push_back
#define ULL unsigned LL
#define mem(a, b) memset(a, b, sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
//#define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout);

const LL mod = 1e9 + 7;

const int maxn = 45000;
int sum[maxn];
LL mul[maxn], inv[maxn];
int qpow(int a, int b) {
	int res = 1;
	LL tmp = a;//注意tmp会爆 涉及乘法
	while (b) {
		if (b & 1)
			res = res * tmp % mod;
		tmp = tmp * tmp % mod;
		b >>= 1;
	}
	return res;
}

void init() {
	sum[1] = 0, mul[1] = inv[1] = 1;
	for (int i = 2; i < maxn; ++i) {
		sum[i] = sum[i - 1] + i;
		mul[i] = mul[i - 1] * i % mod;//由于直接保存累乘结果会爆 所以保存取模结果
		//inv[i] = (mod - mod / i) * inv[mod % i] % mod;//公式 递推求
		inv[i] = qpow(i, mod - 2);
	}
	//cout << inv[2];
}
int main() {
	/*int sum = 0;
	for (int i = 1; i <= 45000; ++i) {
		sum += i;
	}
	printf("%d", sum);*/

	int t;
	scanf("%d", &t);
	init();
	while (t--) {
		int x;
		scanf("%d", &x);
		if (x <= 4) {
			printf("%d\n", x);
			continue;
		}
		//找那么一个序列 2 + 3 + 4 + 。。。+ (最大数P  - 一个负数a)
		int  p = lower_bound(sum + 2, sum + maxn, x) - sum;
		//cout << p << endl;
		//int left = 2, right = maxn, mid;//【left, right) 左闭右开
		//mid = (left + right) >> 1;
		//while (left + 1 < right) {//left + 1 == right 结束
		//	if (sum[mid] > x)
		//		right = mid;
		//	else
		//		left = mid;
		//	mid = (left + right) >> 1;
		//}
		//cout << mid << endl;//手动找的是小于(数不存在的话)等于x的位置
		
		//现在求负数  因为加多了嘛  题目让我们求 这些数的积最大 同时这些数加起来要等于 x   现在加多了 要减走  即在累乘那里 除走
		int a = x - sum[p];
		//cout << a << endl;
		//可以发现 这个a的范围 -k, -(k - 1), - (k - 2), 。。。-2, -1, 0
		//这个k就是(p - 1)
		/*结论:要想乘积最大:连续乘最好 2*3*4*5.。。。
		我们要乘积最大,那么我们把n尽可能的拆分,如果题目不要求ai≠aj,那么我们将x拆分成什么最好呢?
		显然拆成2和3(2为偶数,3为奇数,2x+3y可以表示大于1的所有正整数)是最好的,顺便提下3最多的时候最优,
		为什么不是2,将,6拆成3个2乘积为8,而将6拆成3乘积为9,(不会证)*/

		//现在看下怎么除去(时刻想着两个数相差越小 越优): a为0  不用去
		//a为-1 把2去掉 此时又会多了一个1  加到p上 p变成p+1  即3*4*(5+1) 
		//剩下的情况 都有一个对应的正数  去掉对应的正数 比如a=-5  2*3*4*5
		//如果去掉2和3 乘积是20  去掉5的话是24  不太理解。。。这样就是最优
		//现在有mul【】 要除去相应的数 再模----》逆元
		LL ans;
		//可以发现比如 2*3*4*5=120  当x=13 即a==-1  根据策略 3*4*6=72 
		if (a == -1)
			ans = mul[p] * inv[2] % mod * inv[p] % mod * (p + 1) % mod;
		else if (a == 0)
			ans = mul[p];
		else
			ans = mul[p] * inv[-a] % mod;
		printf("%lld\n", ans);
	}

	return 0;
}


猜你喜欢

转载自blog.csdn.net/liangnimahanwei/article/details/82935881