五一假期,记vp一场c++c组的题目..
A: 拼正方形(结果填空)
题意:给a个2*2的方格,b个1*1的方格,问能凑成的最大正方形边长;
思路:4a+1b开根号就是答案,前提是b得是偶数且是四的倍数,不妨先将b个1*1的方格凑成2*2的,然后2*2的格子数*4后开方,就是答案;
答案:5435122
B: 劲舞团(结果填空)
题意:给2000行记录,求最长连续匹配数,匹配是指:当前记录中 第一二个字符一样 且和上一条记录(同匹配)之间的时间戳差值小于1s;
思路:略
答案:9
C: 数字诗意
题意:给一个数列,问数列中有多少个数是属于一个前n项和的结果(n>=2)。
思路:数据范围显然不可能是枚举
和
,很可能需要位运算,如图,打表找规律,发现只有2的整数次幂才不满足,2的54次方刚好大于了
的数据范围1e16, 所以只需要对枚举54位,数列中恰好等于k次方的数的个数就是答案。
复杂度:O(N)
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int m,res = 0; cin >> m;
for (int i = 1; i <= m; i ++){
ll x; cin >> x;
for (int j = 0; j <= 54; j ++){
if (x == pow(2,j)) res ++;
}
}
cout << res <<"\n";
return 0;
}
D: 封闭图形个数
题意:给10以内数字加一个基础属性,0,4,6,9数字属性值是1,8的属性值是2,1,2,3,5,7的属性值是0,现给一个数列,要求按照属性值大小排序,若属性值大小相同,则输出数字本身大小
思路:结构体基础知识,重构小于号(手写sort的cmp也可以),直接把一个数字按数位拆出来然后叠加属性值就可以了,最后将数列按从小到大顺序输出。
复杂度:O(N)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n;
const int N = 2e5+10;
struct Nod{
int w;
bool operator < (const Nod &k) {
int r1=0,r2=0;
int m1 = w, m2 = k.w;
while (m1){
int x = m1%10;
r1+=((x==0)||(x==4)||(x==6)||(x==9));
r1+=((x==8)*2);
m1/=10;
}
while (m2){
int x = m2%10;
r2+=((x==0)||(x==4)||(x==6)||(x==9));
r2+=((x==8)*2);
m2/=10;
}
if (r1==r2) return w < k.w;
return r1 < r2;
}
}arr[N];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> arr[i].w;
sort(arr+1, arr+n+1);
for (int i = 1; i <= n; i ++) cout << arr[i].w << " ";
return 0;
}
E:回文数组
题意:给一个数列,有两个操作,一是将单个数字+1或-1,二是将相邻的两个数字同时+1或者-1,问最后将其修改为回文数列的最小修改次数。
思路:回文相关的问题一般都具有对称性,考虑第i个数和考虑第n-i+1个数问题一样,可以忽略后者,因此这里只需要处理前一半。贪心,假设现在处理的第j个数与第n-j+1个数的差值 和 即将处理的第j+1个数与第n-(j+1)+1个数的差值同号,那么就可以用操作二一次性处理,剩下的就只能是操作1了。
复杂度:O(N)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 2e5+10;
ll a[N],p[N];
ll res = 0;
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n; cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1, j = n; i < j; i ++, j --) p[i] = a[j] - a[i];
for (int i = 1; i <= n/2; i ++){
res += abs(p[i]); // 异号单独加
if (p[i]>0 && p[i+1]>0) p[i+1]-=min(p[i],p[i + 1]); // 同号一起处理
if (p[i]<0 && p[i+1]<0) p[i+1]-=max(p[i],p[i + 1]);
}
cout << res << "\n";
return 0;
}
F: 商品库存管理
题意:给一个全0数列,有m次操作,每次给数列中一段区间+1,求分别撤销第i次操作后,数列中0的个数。
思路:差分+前缀和。先差分所有操作,然后记录一下当前数列当中0的个数r0,因为差分后是0的数,无论撤销哪一次操作,都不会变。随后,结合题意,假定撤销某次区间
操作之后
,那么也就意味着
在这个区间操作被撤销之前必定是1,显然,问题可以转化为:求m个区间当中1的个数。将差分后的数组中的1拎出来做前缀和,然后分别算每个操作区间当中1的个数就可以了,记得每次都要加上r0。
复杂度:O(M+N)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m;
const int N = 300010;
typedef pair<int,int> PII;
PII ts[N];
int bs[N],pre[N];
int ret = 0;
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i ++){
cin >> ts[i].first >> ts[i].second;
bs[ts[i].first]++,bs[ts[i].second+1]--;
}
for (int i =1;i<=n;i++) bs[i]+=bs[i-1], ret += (bs[i]==0);
for (int i =1;i<=n;i++) pre[i]+=pre[i-1]+(bs[i]==1);
for (int i = 1; i <= m; i ++){
int l = ts[i].first, r = ts[i].second;
cout << pre[r]-pre[l-1]+ret <<"\n";
}
return 0;
}
G: 挖矿
题意:在一维数轴上,给若干个指定坐标x和m,从起始点出发,每次可以朝左边/右边走一个单位,问不超过距离m最多能走到多少个不同的指定坐标。
思路:先预处理一个往左右两边走分别能包含坐标数的前缀和,g[0][i]表示从起始点往左走i个单位长度能包含多少个指定坐标,g[1][i]表示从起始点往右走i个单位长度能包含多少个指定坐标。
接着分析,假设有
(小于0的情况也一样,对称的),若
能被包含到答案里面,那么一定是从
走过来,而不可能从
走过来,这里也不难证明,假设从
到
的路线是优的,那从起始点出发到
是优的、从起始点到
也是优的,显然与前一命题产生矛盾,所以每一个指定坐标只需要考虑是否能从比自己小的坐标走过来。
其次讨论不同情况:情况1:从起始点一直往左走能找到解,答案就是g[0][m];情况2是情况1的镜像,答案是g[1][m]; 情况3:从起始点往左走i个单位,然后往回到起点再往右走(
)个单位;情况4也是情况3的镜像。
可以发现,情况1、2其实是可以和3、4合并的(情况3、4枚举往左/右走的单位长度,当单位长度是m的时候其实就是情况1和2,能往反方向走当且仅当
)。
复杂度:O(N)
#include <iostream>
#include <algorithm>
using namespace std;
int n,m;
const int N = 1e5+10, M = 2e6 + 10;
typedef long long ll;
ll g[2][M];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++){
int x;cin>>x;
if (x<0) g[0][-x]=1;
if (x>=0) g[1][x]=1;
}
// 前缀和 往左右走i步有多少个宝石
for (int i = 1; i <= m; i ++) {
g[0][i]+=g[0][i-1];
g[1][i]+=g[1][i-1];
}
ll Lres = 0;
for (int i = 1; i <= m; i ++){
ll dis = g[0][i];
if (m-2*i>0) dis += g[1][m - 2*i];
Lres = max(Lres,dis);
}
ll Rres = 0;
for (int i = 1; i <= m; i ++){
ll dis = g[1][i];
if (m-2*i>0) dis += g[0][m - 2*i];
Rres = max(Rres,dis);
}
cout << max(Lres,Rres) << "\n";
return 0;
}
H: 回文字符串
题意:给一个字符串,可以在开头处加入任意数目个指定字符l/q/b,问能否将字符串转化为一个回文字符串。
思路:基础题实锤。既然能在字符串前面加入任意数目个l/q/b,那字符串最后面的l/q/b完全可以忽略,因为一定可以配对,问题转化为:删掉给定字符串最后面的l/q/b字符,问剩下的字符是否是回文串,评测一共是10组数据,一组字符串长度最大是1e6,也就是1e7的复杂度,On可以随便写,同时从两边遍历中间,发现对应不上就直接停止。
复杂度:O(N)
#include <iostream>
#include <algorithm>
using namespace std;
void solve(){
string s; cin >> s;
int pos = s.length()-1;
for (; pos; pos --){
if (s[pos]!='l' && s[pos] !='q' && s[pos]!='b') break;
}
string tmp = s.substr(0, pos+1); // 去掉后面那一串前面可以任意堆叠的
bool fg = true;
for (int i = 0,j = tmp.length()-1; i <= j; i ++, j --){ // 看看剩下的是不是回文串就可以了 复杂度On 1E7数据1秒随便跑
if (tmp[i] != tmp[j]){
fg = false;
break;
}
}
cout << (fg ? "Yes" : "No") << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int t; cin >> t;
while (t--){
solve();
}
return 0;
}
Finally:测评成绩
以上仅为oj平台的测评结果( 评测OJ:首页 - DashOJ),若有最优解或者Hack数据欢迎在评论区讨论;
最后的最后,祝大家五一假期快乐!