【笔试】美团2024年春招第二场笔试(技术)
文章目录
- T1 模拟
- T2 模拟
- T3 模拟,快速幂/打表
- T4 众数、前缀和、树状数组
- T5 逆序对,树状数组
T1 模拟
题目:数组求和,判断是否要减一个数
思路:模拟即可
//T1
//AC
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;int main() {int n; cin>>n;LL sum = 0;for(int i = 1; i <= n; i++){LL x; cin>>x;sum += x;}LL a, b; cin>>a>>b;sum = sum-a-b;cout<<sum<<"\n";
}
T2 模拟
题目:
- 1、所有字母都是小写。例如:good
2、所有字母都是大写。例如:APP
3、第一个字母大写,后面所有字母都是小写。例如:Alice - 给个字符串最少几次能合法
思路:
- 最多四种情况判断一下即可
//T2
//AC
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;int main() {string s; cin>>s;int sm = 0;for(int i = 0; i< s.size(); i++){if(islower(s[i])){sm++;}}int bg = s.size()-sm;int ans = min(bg, sm);if(isupper(s[0])){ans = min(ans, bg-1);}cout<<ans<<"\n";
}
T3 模拟,快速幂/打表
- 题目:数组,每次操作将除了第 x 个元素的其余元素翻倍,操作q次,求最后的数组和。
- 思路:先假设每个数每次都翻倍,维护每个数没有翻倍的次数,最后除回去就行。
//T3
//AC
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+10;
const LL mod = 1e9+7;
LL a[maxn], v[maxn];
LL pows[maxn];
int main() {pows[0] = 1;for(int i = 1; i < maxn; i++){pows[i] = pows[i-1]*2%mod;}int n, q; cin>>n>>q;for(int i = 1; i <= n; i++){cin>>a[i];}LL fb = q;while(q--){int xi; cin>>xi;v[xi]++;}LL ans = 0;for(int i = 1; i <= n; i++){ans = (ans + (pows[fb-v[i]]*a[i]%mod))%mod;}cout<<ans<<"\n";
}
T4 众数、前缀和、树状数组
题目:求数组的所有子数组的众数之和,众数有多个时取小的那个。
输入
3
2 1 2
输出
9
思路1:
- 取值只有1和2,肯定要用起来。考虑对答案的贡献,默认所有区间+1,因为至少1嘛。然后最多也就是2,什么情况下会是2呢,对于区间和>区间长度*1.5的,也就是2的个数超过区间一半的时候。
- 暴力所有区间n^2,然后前缀和O1区间和判断累加,到这能拿70%(其实也就是找2是众数的区间有多少个的情况)
//T4-70%
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e5+10;
LL a[maxn], s[maxn];
int main() {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);LL n; cin>>n;LL ans = 0;for(LL i = 1; i <= n; i++){cin>>a[i];// a[i]--;// ans += a[i];s[i] = s[i-1]+a[i];}// for(LL l = 1; l <= n; l++){// for(LL r = l; r <= n; r++){// LL sum = s[r]-s[l-1];// LL c2 = sum-(r-l+1);// LL c1 = (r-l+1)-c2;// // for(int k = l; k <= r; k++){// // if(a[k]==1)c1++;// // else c2++;// // }// if(c1>=c2)ans += 1;// else ans += 2;// }// }// 找出区间和比1.5倍区间长度要长的,+2(或者说加+1,然后加全部区间个数)ans = (1+n)*n/2;for(int len = 1; len <= n; len++){for(int i = 1; i+len<=n; i++){if((s[i+len-1]-s[i-1]) > len+len/2+len%2)ans++;}}cout<<ans<<"\n";// LL nn = (1+n)*n/2;// LL s1 = 0, s2 = 0;// for(int i = 1; i <= n; i++){// LL x; cin>>x;// if(x==1)s1++;// else s2++;// }// cout<<nn*s2/s1<<"\n";
}
思路2:
- 考虑区间枚举怎么优化,考虑区间总个数一样,换成求1是众数的的区间有多少个。
- 把1和2换成-1和1。数组的众数是 1 等价于数组的和不小于 0,此时只需要找出有多少个子数组的和不小于 0 (剩下的就是众数2)。 这里还是前缀和, 前面有几个前缀和 ≤ 当前的前缀和。
- 这里有点类似于求逆序对时候的做法,用下标为前缀和,权值为 1 的树状数组 统计某个取值范围内,有多少个前缀和。
//T4-AC
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL maxn = 2e5+10;
LL n, v[maxn*2+10];
void add(LL x, LL y){for(LL i = x; i <= 2*maxn; i += i&(-i)){ // n-> 2maxnv[i] += y;}
}
LL query(LL x){LL res = 0;for(LL i = x; i > 0; i -= i&(-i)){res += v[i];}return res;
}
int main() {cin>>n;LL t = 0, res = 0;add(maxn, 1LL); //有负数,需要转换一下for(LL i = 1; i <= n; i++){LL x; cin>>x;if(x==1LL)t++; else t--;res += query(t+maxn);// cout<<res<<"\n";add(t+maxn, 1LL);}LL cnt = n*(n+1)/2;// cout<<res<<"\n";cout<<res+2*(cnt-res)<<"\n";
}
T5 逆序对,树状数组
题目:给你一个排列,定义f(i)为a[i]取反后形成的数组的逆序对数量,求f(1)到f(n)的值。
思路:不难想到,先来个原先的逆序对。 然后扫一遍,过程中维护一下左右比当前数的大小关系,推导一下公式算一下即可。
//T5
//AC
// = 逆序对-l大于ai-r小于ai+l全部
// = 逆序对+l小于ai-r小于ai
// 排列:ai-1 = l小于ai+r小于ai,维护左边比ai小的数的个数
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL maxn = 200010;
struct node{ LL v, p; }a[maxn];
bool cmp(node x, node y){return x.v<y.v; }
LL n;
LL v[maxn], res[maxn];
void add(LL x, LL y){for(LL i = x; i <= n; i += i&(-i)){v[i] += y;}
}
LL query(LL x){LL res = 0;for(LL i = x; i > 0; i -= i&(-i)){res += v[i];}return res;
}
int main() {cin>>n;for(LL i = 1; i <= n; i++){cin>>a[i].v; a[i].p = i;}sort(a+1,a+n+1,cmp);LL ans = 0;for(LL i = n; i>= 1; i--){// cout<<a[i].v<<"\n";add(a[i].p,1);ans += query(a[i].p-1);LL lmax = query(a[i].p-1);LL lmin = a[i].p-1-lmax;LL rmin = a[i].v-1-lmin;res[a[i].p] = lmin-rmin;}// cout<<ans<<"\n";for(LL i = 1; i <= n; i++){cout<<ans+res[i]<<" ";}
}
// 64 位输出请用 prLLf("%lld")