给定一个正整数 nnn,用 nnn 文钱买 nnn 只鸡,问公鸡、母鸡、小鸡各买多少只?(公鸡 555 元一只,母鸡 333 元一只,小鸡 111 元 333 只)
你只需求出解的数量即可。
题目思路:
- 首先我们可以想到,用三重循环枚举公鸡、母鸡、小鸡的数量,如果满足要求将答案加 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;
}
- 经过简单思考可知,实际上我们可以剪掉一重循环,比如说 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;
}
- 当 nnn 的值进一步扩大时,我们应当怎么办呢?这时就需要引进数学思维了。设公鸡的数量为 xxx,母鸡的数量为 yyy,可以列出方程:5x+3y+n−x−y3=n5x+3y+\frac{n-x-y}{3}=n5x+3y+3n−x−y=n,可以解得 y=n−7x4y=\frac{n-7x}{4}y=4n−7x,这样我们只需要用一层循环枚举公鸡的数量即可。时间复杂度进一步优化至 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;
}
- 计蒜客上有一道题将 nnn 扩大到了 101810^{18}1018,即使已经很优的线性复杂度也肯定会超时。那么怎么办呢?计蒜客提供了一种扩展欧几里得算法的解法,时间复杂度为 O(logn)\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,那么答案是否为单调不降呢?实际上不是。我们可以从 111 到 282828 所有的数都枚举一遍,可以发现,当 nnn 模 282828 的数为一些数(具体见代码)时,答案的值是要比其他少 111 的。这样,我们就可以使用 O(1)\mathcal{O}(1)O(1) 的时间复杂度通过本题。
O(logn)\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;
}