作为一名参加过多次算法竞赛的选手,我深知在比赛中快速理解题意并找到最优解的重要性。今天我将分享牛客寒假算法基础集训营中的五道典型题目,从解题思路到代码实现,带你深入理解这些问题的本质。
这道题的核心在于寻找一个最小的正整数x,使得n×x的结果个位数为0。这实际上考察了我们对数字特性和模运算的理解。
关键在于观察n的个位数,因为乘法结果的个位数只由乘数的个位数决定。例如:
根据个位数的不同,我们可以将问题分为几种情况:
个位数为1,3,7,9:这些数字与10互质,最小需要乘以10才能使乘积个位数为0。因为10是这些数字在模10下的乘法逆元。
个位数为5:由于5×2=10,所以最小x为2。
个位数为0:任何数乘以1都保持不变,所以x最小为1。
个位数为2,4,6,8:这些偶数与5相乘都能得到个位数为0的结果,所以x最小为5。
cpp复制#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define endl '\n'
const int N=1e6+6;
int n;
void solve() {
cin>>n;
int x=n%10,k;
if(x==1||x==3||x==7||x==9) k=10;
else if(x==5) k=2;
else if(x==0) k=1;
else k=5;
cout<<k<<endl;
}
signed main() {
IOS;
int _=1;
while(_--) solve();
return 0;
}
注意:代码中使用了
#define int long long来防止整数溢出,这在算法竞赛中是常见做法,但在生产环境中应谨慎使用。
题目要求构造一个长度为n的数组,使得对于任意i,a[i]不等于其左右邻居的平均值。换句话说,没有元素恰好等于其左右邻居的算术平均数。
通过小规模枚举可以发现:
对于n≥4的情况,可以证明不存在满足条件的数组:
cpp复制#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define endl '\n'
const int N=1e6+6;
int n,a[N];
void solve() {
cin>>n;
if(n==1) {
cout<<"YES"<<endl<<1<<endl;
return;
}
else if(n==3) {
cout<<"YES"<<endl<<"1 2 3"<<endl;
return;
}
else cout<<"NO"<<endl;
}
signed main() {
IOS;
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
实际应用提示:这类构造性问题在竞赛中常见,通常需要从小规模案例入手寻找规律。
题目允许我们将数组中任意开区间内的元素变为该区间端点值的最大值。我们的目标是最大化整个数组的和。
关键观察:
设数组为a[1..n],最大值为M,则:
这是因为我们可以对每个中间元素单独操作,将其变为max(a[1],a[n],M),而M必然是其中的最大值。
cpp复制#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define endl '\n'
const int N=1e6+6;
int n,a[N];
void solve() {
cin>>n;
int mx=-1;
for(int i=1;i<=n;i++) {
cin>>a[i];
mx=max(mx,a[i]);
}
if(n==1) cout<<a[1]<<endl;
else if(n==2) cout<<(a[1]+a[2])<<endl;
else cout<<(mx*(n-2)+a[1]+a[n])<<endl;
}
signed main() {
IOS;
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
性能考虑:算法时间复杂度是O(n),已经是最优解。注意使用long long防止求和时溢出。
题目描述的是在一个数组的最左侧插入一个数字k后形成环形数组,要求找出相邻元素和的最大值。
关键点:
解决方案:
时间复杂度O(n),空间复杂度O(1)(可以边读入边处理)
cpp复制#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define endl '\n'
const int N=1e6+6;
int n,a[N],k;
void solve() {
cin>>n>>k;
a[0]=k;
int mx=-1e18;
for(int i=1;i<=n;i++) {
cin>>a[i];
mx=max(a[i]+a[i-1],mx);
}
mx=max(mx,k+a[n]);
cout<<mx<<endl;
}
signed main() {
IOS;
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
调试技巧:初始化mx为一个很小的值(如-1e18)而不是0,防止所有和都为负数时出错。
这是一个关于卡牌游戏的排列组合问题。我们需要计算小苯能够获得最大分数的排列方式数量。
关键观察:
证明为什么这种排列最优:
cpp复制#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define endl '\n'
const int N=1e6+6;
const int mod=998244353;
int n,a[N],b[N],k;
int Jie(int x1,int x2) {
int s1=1,s2=1;
for(int i=1;i<=x1;i++) s1=(s1*i)%mod;
for(int i=1;i<=x2;i++) s2=(s2*i)%mod;
if(x1==0) return s2;
if(x2==0) return s1;
return (s1*s2)%mod;
}
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int mi=1e18;
for(int i=1;i<=n;i++) {
cin>>b[i];
mi=min(mi,b[i]);
}
int m1=0; // 大于b中最小值的
for(int i=1;i<=n;i++) {
if(a[i]>mi) m1++;
}
int ans=Jie(m1,n-m1);
cout<<ans<<endl;
}
signed main() {
IOS;
int _=1;
cin>>_;
while(_--) solve();
return 0;
}
模运算注意点:题目要求结果对998244353取模,因此在计算阶乘时要边计算边取模,防止溢出。