百鸡百钱问题-从枚举到数学

本文探讨如何从枚举方法优化到数学公式解决百鸡百钱问题,通过减少循环次数降低时间复杂度,最终实现O(1)的解题方案。分析了n值变化对解的影响,并指出特定情况下答案会减少1。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定一个正整数 nnn,用 nnn 文钱买 nnn 只鸡,问公鸡、母鸡、小鸡各买多少只?(公鸡 555 元一只,母鸡 333 元一只,小鸡 111333 只)

你只需求出解的数量即可。

题目思路:

  1. 首先我们可以想到,用三重循环枚举公鸡、母鸡、小鸡的数量,如果满足要求将答案加 111 即可。这种解法的时间复杂度为 O(n3)\mathcal{O}(n^3)O(n3)
#include<iostream>
using namespace std;
int main(){
    int n,ans=0;
    cin>>n;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            for(int k=0;k<=n;k++)
                if(5*i+3*j+k/3==n&&k%3==0&&i+j+k==n)
                    ans++;
    cout<<ans;
    return 0;
}
  1. 经过简单思考可知,实际上我们可以剪掉一重循环,比如说 n=100n=100n=100,当 i=4,j=18i=4,j=18i=4,j=18时,我们可以直接推出 k=78k=78k=78,只需验证费用和是否为 nnn 即可。时间复杂度优化至 O(n2)\mathcal{O}(n^2)O(n2)
#include<iostream>
using namespace std;
int main(){
    int n,ans=0;
    cin>>n;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++){
            int k=n-i-j;
            if(k%3==0&&5*i+3*j+k/3==n)ans++;
        }
    if(ans)cout<<ans;
    else cout<<"No Answer.";
    return 0;
}
  1. nnn 的值进一步扩大时,我们应当怎么办呢?这时就需要引进数学思维了。设公鸡的数量为 xxx,母鸡的数量为 yyy,可以列出方程:5x+3y+n−x−y3=n5x+3y+\frac{n-x-y}{3}=n5x+3y+3nxy=n,可以解得 y=n−7x4y=\frac{n-7x}{4}y=4n7x,这样我们只需要用一层循环枚举公鸡的数量即可。时间复杂度进一步优化至 O(n)\mathcal{O}(n)O(n)
#include<iostream>
using namespace std;
int main(){
    int n,ans=0;
    cin>>n;
    for(int x=0;n-7*x>=0;x++)
        if((n-7*x)%4==0){
            int y=(n-7*x)/4;
            if(n-x-y>=0&&(n-x-y)%3==0)ans++;
        }
    if(ans)cout<<ans;
    else cout<<"No Answer.";
    return 0;
}
  1. 计蒜客上有一道题将 nnn 扩大到了 101810^{18}1018,即使已经很优的线性复杂度也肯定会超时。那么怎么办呢?计蒜客提供了一种扩展欧几里得算法的解法,时间复杂度为 O(log⁡n)\mathcal{O}(\log n)O(logn),可以通过该数据,不过这不是我们要重点介绍的算法,读者们可以自行了解其算法内涵。我们要重点介绍的算法在 O(n)O(n)O(n) 算法的基础上继续使用数学思维。通过举例可以发现,nnn 的值每增加 282828,即 lcm⁡(4,7)\operatorname{lcm}(4,7)lcm(4,7),答案就增加 111,那么答案是否为单调不降呢?实际上不是。我们可以从 111282828 所有的数都枚举一遍,可以发现,当 nnn282828 的数为一些数(具体见代码)时,答案的值是要比其他少 111 的。这样,我们就可以使用 O(1)\mathcal{O}(1)O(1) 的时间复杂度通过本题。

O(log⁡n)\mathcal{O}(\log n)O(logn) 的做法:

#include<cstdio>
#include<algorithm>
#include<iostream>
#define int long long
using namespace std;
inline void exgcd(int a,int b,int &d,int &x,int &y){
	if(!b){d=a,x=1,y=0;return;}
	exgcd(b,a%b,d,y,x);
	y-=a/b*x;
}
signed main(){
	int a=3,b=4,d=1,x0,y0,n;
	cin>>n;
	exgcd(a,b,d,x0,y0);
	if((n*3)%d==0){
		int x1=x0*3*n;
		int y1=y0*3*n;
		int maxx=min(-x1/b,y1/a);
		int minn=(-(n+x1-y1)-1)/(a+b)+1;
		if(maxx>=minn)cout<<maxx-minn+1<<endl;
		else cout<<"No Answer."<<endl;
	}else cout<<"No Answer."<<endl;
	return 0;
}

O(1)\mathcal{O}(1)O(1) 的做法:

#include<cstdio>
#define int long long
int a[]={1,2,3,5,6,9,10,13,17};
signed main(){
    int n;
    scanf("%lld",&n);
    int ans=1;
    for(int i=0;i<9;i++)
        if(n%28==a[i])ans=0;
    if(n/28+ans==0)puts("No Answer.");
    else printf("%lld",n/28+ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值