LIGHT OJ 1189 - Sum of Factorials【贪心+递归】

本文探讨了如何解决一个整数是否能表示为若干个不同阶乘数之和的问题,并提供了一种有效的贪心算法解决方案。通过对输入整数进行递归拆解,找到构成该整数的所有阶乘数。

1189 - Sum of Factorials

Time Limit: 0.5 second(s)Memory Limit: 32 MB

Given an integer n, you have to find whether it can be expressed as summation of factorials. For given n, you have to report a solution such that

n = x1! + x2! + ... + xn! (xi < xj for all i < j)

Input

Input starts with an integer T (≤ 10000), denoting the number of test cases.

Each case starts with a line containing an integer n (1 ≤ n ≤ 1018).

Output

For each case, print the case number and the solution in summation of factorial form. If there is no solution then print'impossible'. There can be multiple solutions, any valid one will do. See the samples for exact formatting.

Sample Input

Output for Sample Input

4

7

7

9

11

Case 1: 1!+3!

Case 2: 0!+3!

Case 3: 1!+2!+3!

Case 4: impossible

Note

Be careful about the output format; you may get wrong answer for wrong output format.


题意:N如果可以表示成某几个数的阶乘则找出这几个数,不能的话输出impossible,不能有x相同的的数;

思路:N达到lld的限度o(N)的复杂度都不行,不能打表所有数,但是可以打出阶乘的表因为20!>2*10^18,就19项就行了,那所有数的组合就是2^19,6位数打表能打,但是所有数之和加起来lld都存不了dp就pass了,只有一个一个判断喽, 了N要么直接就等于一个数的阶乘要么大于一个数的阶乘,等于的话直接记录这个数就行,如果大于的话,要组成阶乘的形式,必须要选这个数,如果不选,那么比这个数小的所有数的阶乘之和都不能取代这个数,这就是贪心选择了,每次找到一个<=N的最大的阶乘,然后用N减去这个阶乘形成了有一个N,递归判断就行;
失误:这题做了一天,想了好久想出来了阶乘的性质(N!>=0!+1!+...+(N-1)!   N=2时等号成立)但是一个递归把我难住了,写不好,又忽视了一个条件 就是不能有相同的,还是递归的条件考虑的不清楚,以后写递归时要注意了 (例如DFS);

AC代码:
#include<cstdio>
#include<cstring>

typedef long long LL;

LL a[33],ord[33],cnt=0;
bool vis[30];//一夜没睡状态不好 错误百出循环条件 数组名称 

bool solve(LL N)
{
	LL i=0;
	if(N==0) return true;
	for(i=19;i>=0;--i)
	{
		if(N>=a[i]&&!vis[i]) {
			break;
		}
	}
	if(i==-1) return false;//<=N的都选完了 N还不为0 
	ord[++cnt]=i;
	vis[i]=true;
	if(solve(N-a[i])) return true;
	return false;
}
int main()
{
	LL i,T,N,Kase=0;
	a[0]=1; for(i=1;i<=20;++i) a[i]=a[i-1]*i;
	scanf("%lld",&T);
	while(T--)
	{
		memset(vis,false,sizeof(vis)); 
		scanf("%lld",&N); cnt=0;
		printf("Case %lld: ",++Kase);
		if(solve(N)) {
			for(i=cnt;i>=1;--i)
			{
				if(i<cnt) printf("+");
				printf("%lld!",ord[i]);
			}
			printf("\n");
		}
		else {
			printf("impossible\n");
		}
	}
	return 0;
 } 


```markdown # 代码概述 该程序旨在处理多组输入数据,每组数据包含两个整数 $n$ 和 $m$,随后读取长度为 $n$ 的整数数组。程序计算所有连续 $m$ 个元素的子数组和,并找出其中的最大值。最终输出最大子数组和,并持续处理直到 $n = 0$ 且 $m = 0$。 然而,在在线评测系统中提交时出现了 **时间超限(Time Limit Exceeded, TLE)** 的错误。 --- # 代码解析 ```c #include<stdio.h> int main(void) { long long int n, m, num1, num2, c, num3, n1, c2, m3; scanf("%lld%lld", &n, &m); while(n != 0 && m != 0) { long long int sum[n]; // 存储每个长度为 m 的子数组和 int x[n], a = 0; // 输入数组 x,a 为索引 n1 = n; m3 = m; // 输入 n 个数 while(n-- > 0) { scanf("%d", &x[a]); a++; } num2 = 0; num1 = 0; c = n1 - m + 1; // 子数组总数 c2 = c; // 计算每一个长度为 m 的子数组和 while(c-- > 0) { num1 = num2; m3 = m; sum[num2] = 0; while(m3-- > 0) { sum[num2] += x[num1]; num1++; } num2++; } // 手动找最大值 num3 = 0; while(c2 > 1) { if(sum[num3] >= sum[num3+1]) sum[num3+1] = sum[num3]; c2--; num3++; } num3--; printf("%lld\n", sum[num3+1]); // 继续下一轮输入 scanf("%lld%lld", &n, &m); } return 0; } ``` ### ❌ 时间超限原因分析: 1. **暴力计算子数组和** 对每个起始位置都重新累加 $m$ 个元素,时间复杂度为 $O(n \times m)$ 每组测试用例。当 $n$ 和 $m$ 较大时(如 $10^5$),这将导致高达 $10^{10}$ 数量级的操作,远超时限允许范围。 2. **未使用滑动窗口优化** 实际上可以利用前一个子数组和快速推出下一个子数组和(即滑动窗口法),避免重复计算。 3. **最大值查找方式低效但非主因** 虽然最大值查找用了类似冒泡的方式,复杂度为 $O(k)$($k = n - m + 1$),虽可优化但仍属于线性,不是性能瓶颈主因。 --- # 知识点(列出该代码中遇到的知识点) ## 1. 滑动窗口(Sliding Window) 通过维护一个窗口内的和,每次移动时减去左端元素、加上右端元素,实现 $O(1)$ 更新,总复杂度降为 $O(n)$。 ## 2. 时间复杂度分析 算法执行步数随输入规模增长的趋势。本题暴力解法为 $O(nm)$,无法通过大数据规模测试。 ## 3. 标准输入输出控制 使用 `scanf` 循环读取多组数据直至终止条件,常见于 OJ 题目输入格式处理。 ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值