牛客小白月赛101题解
前言:
主要是有学弟在补这一场,所以出一个题解,毕竟我也是半个新手,所以简单的写个题解,作为参考。
A.tb的区间问题
个人题解:
首先踩一下复杂度, 5 e 3 5e3 5e3的话,那么 n 2 n^2 n2就是 2.5 e 7 2.5e7 2.5e7那应该是可以过的。最后输出数组和的最大值
那么你很明显就能想到:因为这东西是连续的一段,那么应该用前缀和来做。
那么你只需要循环枚举一下区间的起点和终点然后直接一减就行了对吧。
#include<iostream>
#include<cstring>
using namespace std;
const int N=5e3+100;
long long a[N];
int main(){
int n, k;
cin>>n>>k;
memset(a, 0, sizeof a);
for(int i=1; i<=n; ++i){
cin>>a[i];
a[i]+=a[i-1];
}
k=n-k;
long long ans=-1;
for(int i=1; i<=n; ++i){
if(i+k-1<=n){
ans=max(ans, a[i+k-1]-a[i-1]);
}
}
cout<<ans<<endl;
return 0;
}
应小黄要求贴一下C
#include<stdio.h>
#include<string.h>
const int N=5e3+100;
long long a[N];
long long maxn(long long a, long long b){
return a>b?a:b;
}
int main(){
int n, k;
scanf("%d%d", &n,&k);
memset(a, 0, sizeof a);
for(int i=1; i<=n; ++i){
scanf("%lld", &a[i]);
a[i]+=a[i-1];
}
k=n-k;
long long ans=-1;
for(int i=1; i<=n; ++i){
if(i+k-1<=n){
ans=maxn(ans, a[i+k-1]-a[i-1]);
}
}
printf("%lld", ans);
return 0;
}
B. tb的字符串问题
那么这个题你会发现有种消消乐的感觉对吧。
对于这种情况你的思路是:
- 应消尽消
- 中间有形如ABA(相同能消)的形式是包不可能消掉的
这种情况首选栈。
如果栈顶是f或t,你手里刚好是c和b,那就把ft弹出来组团扔掉,如果不是就压进栈。
这里提一嘴用C模拟栈:
开一个整型的数组,和一个idx表示这个栈存了多少数字,然后idx=-1表示空。压栈就是++idx,取用就是idx然后idx–就可以了,手推一下直接贴代码
C:
#include<stdio.h>
const int N=1e6+100;
char a[N];
char s[N];
int idx=-1;
int main(){
int n;
scanf("%d", &n);
getchar();
for(int i=1; i<=n; ++i){
scanf("%c", &a[i]);
}
for(int i=1; i<=n; ++i){
if(idx!=-1 && ((s[idx]=='f' && a[i]=='c') || (s[idx]=='t' && a[i]=='b'))){
idx--;
if(idx<0)idx=-1;
}
else s[++idx]=a[i];
}
printf("%d\n", idx+1);
return 0;
}
C++
#include<iostream>
#include<stack>
using namespace std;
int n;
string str;
int main(){
cin>>n;
cin>>str;
stack<char>stk;
stk.push(0);
for(int i=0 ;i <str.size(); ++i){
if(str[i]=='c' && stk.top()=='f')stk.pop();
else if(str[i]=='b' && stk.top()=='t')stk.pop();
else stk.push(str[i]);
}
int s=0;
while(stk.empty()==0){
stk.pop();
s++;
}
cout<<s-1<<endl;
return 0;
}
C.tb路径问题
分析:
这题完全不需要算法,是一个推理,如果你要到 ( n , n ) (n,n) (n,n),那么肯定不能直接到。而且你也不能直接到 ( n − 1 , n ) (n-1, n) (n−1,n),因为这个数的最大公约数是1。证明就是欧几里得算法的证明。 ( n − 2 , n ) (n-2, n) (n−2,n)来说需要一个分类讨论,如果是偶数的话那么显然 g c d gcd gcd就是 2 2 2了对吧。 2 2 2是你最好把控的一个数,因为它是终点距离不是1的数最近的一个点。而且恰好是距离起点不是一的一个点。
在这里n是奇数的情况略掉。
C++
#include<iostream>
using namespace std;
int main(){
int n;
cin>>n;
if(n==2){
cout<<2<<endl;
return 0;
}
if(n%2==0){
cout<<4<<endl;
return 0;
}
if(n==1){
cout<<0<<endl;
return 0;
}
if(n==3){
cout<<4<<endl;
return 0;
}
if(n%2==1){
cout<<6<<endl;
return 0;
}
return 0;
}
D.tb的平方问题
这东西我应该是做的shi了。应该有更简单的方法。
这道题的优势在于 n n n并不大,所以还是可以二维去做的,直接类似A前缀和然后判断一下是不是平方数就可以了。我这里平方数判断非常的shi,但是我懒得改了。正确的方法是利用sqrt判断开完根号是不是整数。我居然在这里写了个二分,不得不说是我喝多了。
然后就是统计区间的问题。
我们发现最终的结果是不会变得,也就是说一个位置 x x x有多少个平方和区间是只要把数列给出来就确定的 ⇒ \Rightarrow ⇒更改n次区间,统计一次 ⇒ \Rightarrow ⇒差分数组。
那么此题可解。
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e3+100;
int a[N];
int b[N];
int net[N*N];
bool check(int x){
int l=1, r=10000;
while(l<r){
int mid=l+r>>1;
if(net[mid]<x)l=mid+1;
else r=mid;
}
if(net[l]==x)return true;
return false;
}
int main(){
for(int i=1; i<=10000; ++i){
net[i]=i*i;
}
int n, q;
cin>>n>>q;
a[0]=0;
for(int i=1; i<=n; ++i){
cin>>a[i];
a[i]+=a[i-1];
}
memset(b, 0, sizeof b);
for(int i=1; i<=n; ++i){
for(int j=i; j<=n; ++j){
int x=a[j]-a[i-1];
if(check(x)){
// cout<<x<<' '<<i<<' '<<j<<endl;
b[i]++;
b[j+1]--;
}
}
}
// for(int i=0; i<=n; ++i)cout<<b[i]<<' ';
// cout<<endl;
for(int i=1; i<=n; ++i)b[i]+=b[i-1];
// for(int i=0; i<=n; ++i)cout<<b[i]<<' ';
for(int i=1; i<=q; ++i){
int x;
cin>>x;
// cout<<'A'<<x<<endl;
cout<<b[x]<<endl;
}
return 0;
}
E.tb的数数问题
一个数的约数显然比他自己要小,而且这是一个集合就说明了你肯定从小到大排个序会更好解决。
我的方法非常的菜。其实有更好的解决方案。
说我自己的思路:
因为最大就只有1e6而已,所以开个 r e t ret ret数组用于操作每个不同的 a i a_i ai,对于每次循环, r e t [ k ∗ a [ i ] ] + + ret[k*a[i]]++ ret[k∗a[i]]++
这样你就得到了集合里的每个数都是谁的约数,谁一共有多少个约数在集合里。
然后你再判断一下这个数有多少约数,判断方法是算数基本定理。
如果两个数相等就 a n s + + ans++ ans++就OK了
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1e6+100;
#define endl '\n'
int a[N];
int ans[N];
int net[N];
bool red[N];
bool fuc[N];
vector<int>prime;
bool p[N];
void make1(){
memset(p, 0, sizeof p);
p[1]=true;
for(int i=2; i<N; ++i){
if(!p[i])prime.push_back(i);
else continue;
for(int j=i+i; j<N; j+=i){
p[j]=true;
}
}
}
void make2(int x){
int u=x;
vector<int>m;
m.clear();
for(int j=0; j<prime.size(); ++j){
if(prime[j]>sqrt(u))break;
if(u%prime[j]==0){
int num=0;
while(u%prime[j]==0){
u/=prime[j];
num++;
}
m.push_back(num);
}
}
if(u!=1)m.push_back(1);
net[x]=1;
for(int j=0; j<m.size(); ++j){
net[x]*=(m[j]+1);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
make1();
// make2();
int n;
cin>>n;
int ma=-1;
memset(net, -1, sizeof net);
for(int i=1; i<=n; ++i){
cin>>a[i];
if(net[a[i]]==-1)make2(a[i]);
ma=max(a[i], ma);
}
sort(a+1, a+n+1);
memset(ans, 0, sizeof ans);
memset(red, 0, sizeof red);
for(int i=1; i<=n; ++i){
int x=a[i];
if(red[x]==1)continue;
red[x]=1;
for(int j=x; j<=ma; j+=x){
ans[j]++;
}
}
int res=0;
memset(fuc, 0, sizeof fuc);
for(int i=1; i<=n; ++i){
if(net[a[i]]==ans[a[i]]){
if(fuc[a[i]])continue;
res++;
fuc[a[i]]=true;
}
}
cout<<res<<endl;
return 0;
}
F.tb的排列问题
这玩意我也不咋会,最后几分钟做出来的。
对b做并查集操作。然后计算集合权重进而求解
#include <bits/stdc++.h>
using namespace std;
typedef long long long long;
const int maxn = 200100;
const long long mod = 998244353;
long long n, m, a[N], b[N], net[N], w[N];
bool st[N];
void solve() {
cin>>n>>m;
for (int i = 1; i <= n; ++i) {
cin>>a[i];
st[i] = 0;
}
for (int i = 1; i <= n; ++i) {
cin>>b[i];
net[b[i]] = i;
}
for (int i = 1; i <= n; ++i) {
w[i] = w[i - 1];
if (a[i] != -1) {
a[i] = net[a[i]];
st[a[i]] = 1;
if (i > a[i] + m) {
cout<<"0"<<endl;
return;
}
} else {
++w[i];
}
}
long long ans = 1, idx = 0;
for (int i = 1; i <= n; ++i) {
if (!st[i]) {
long long t = min(i + m, n);
ans = ans * (w[t] - idx) % mod;
++idx;
}
}
cout<<ans<<endl;
}
int main() {
int t = 1;
cin>>t;
while (t--) solve();
return 0;
}
赛后总结
整体的难度其实个人感觉比较低,前5个题的话应该在cf大概1000~1300的状态,整体相对难度不大。然后F是一个不是很防AK的防AK,我做着应该是有点吃力了。