校内随便弄了一场训练赛,用于检测大家现在的代码水平,因为出于训练目的,我精选了部分我写过的一些比较好的题目,以及一些经典的题目。里面的题都是改编题or原题or模板题,改编题基本是原题的弱化版,不过根据赛后的结果来看,大家的过题情况不是很好。不过个人认为,本次训练在目的在于让同学见识更多的题型与知识点,里面基本没有很高深的知识点,给21级新生做练习题,以此来进行一些算法模板的学习也是很好的。
由于期末了时间紧迫,组题的时候并没有准备好写题解。不过有很多同学来问了就写一下我出的部分内容的题解吧。顺便把训练赛的题目放到了codeforces上(私密比赛),如果有别的学校的新生想要训练也可以尝试一下,希望在后面的题解中都会将原题链接放出,如果简化后的题目写出来了也可以写原题尝试一下。
比赛链接:JXNU-Trail for Nowcoder Summer Camp(Off-Campus)
A. eroengine’s operation
预期难度:Hard
给定一个 正整数 q q q ,实现 q q q 次操作,往数组中添一个数 x x x 或查询最大的 a [ i ] & x a[i] \& x a[i]&x 。 ( q ≤ 2 e 5 , x < 2 20 ) (q \leq 2e5,x < 2^{20}) (q≤2e5,x<220)
这道题是Sequence的简化版。
tag:按位贪心+记忆化搜索
优先考虑操作
2
2
2 查询操作,对于一个给定的数
x
x
x,它的所有二进制位是
0
0
0 的位都是对答案没有用处的,所以只需要考虑所有其二进制位为
1
1
1 的位。
考虑一个简单的贪心,从高位
1
1
1 判断到低位
1
1
1 ,假设某一位的
a
[
i
]
a[i]
a[i] 存在是
1
1
1 的点,那么将所有该位二进位是
0
0
0 的
a
[
i
]
a[i]
a[i] 删去。显然,对于单次的查询操作复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 。这样的复杂度是不能接受的。
考虑优化(本题重点),我们知道
a
[
i
]
&
x
a[i] \& x
a[i]&x 的含义是
a
[
i
]
a[i]
a[i] 和
x
x
x 取交集。那么我们可以考虑在添加每一个数的时候加上自身所有的子集那么查询的时候便可以从高位到低位逐位考虑,复杂度降至
O
(
l
o
g
n
)
O(logn)
O(logn) 。
最后考虑实现如何每次添加一个数的所有子集,每次遍历子集的做法复杂度显然不对,但是容易考虑到每个集合至多被添加一次,所以这个过程可以通过记忆化搜索实现。
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define mod 998244353
using namespace std;
string no="no\n",yes="yes\n";
const int inf=1e15;
int n,m,l,r,k,res;
int vis[2000005];
void add(int x){
if(vis[x])return;
vis[x]=1;
for(int i=19;i>=0;i--){
if(x>>i&1){
add(x^(1<<i));
}
}
}
int ask(int x){
int res=0;
for(int i=19;i>=0;i--){
if(x>>i&1){
if(vis[res^(1<<i)]){
res^=(1<<i);
}
}
}
return res;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>m>>k;
if(m==1){
add(k);
}
else{
cout<<ask(k)<<endl;
}
}
}
void main_init(){}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
cout<<fixed<<setprecision(12);
main_init();
int _=1;
//cin>>_;
while(_--){
solve();
}
}
B. eroengine’s Confusion
签到题,输出
a
∗
b
−
a
−
b
a*b-a-b
a∗b−a−b 即可。
给了很多组样例用于猜结论,详细结论请自行百度搜索小凯的疑惑。
C. Small Multiple
完全原题,ARC084_d。增强一点建图的见识,让同学知道图论题有的时候题面根本不会出现图。
D. Binary-Search God of War
预期难度:middle
这道题是Difference的简化版。
题意:
f
(
l
,
r
)
=
∑
i
=
l
r
a
i
f(l,r)= \sum_{i=l}^{r}{a_i}
f(l,r)=i=l∑rai
给定一个数组
a
a
a 求第
k
k
k 大的
f
(
l
,
r
)
f(l,r)
f(l,r) 。
tag:二分答案。
第一步二分答案,然后通过统计大于等于
m
i
d
mid
mid 的区间个数总共有多少个。
思考 check 的写法,因为数组中所有数都是正整数,所以我们容易得到一件事情,对于固定的
r
r
r, 假设
f
(
l
,
r
)
≥
m
i
d
f(l,r) \geq mid
f(l,r)≥mid 成立,那么
f
(
l
−
1
,
r
)
≥
m
i
d
f(l-1,r) \geq mid
f(l−1,r)≥mid 同样成立。所以我们可以与处理出前缀和,对于固定的
r
r
r 可以二分出满足
f
(
l
,
r
)
<
m
i
d
f(l,r) < mid
f(l,r)<mid 成立的最小的
l
l
l,然后统计答案即可。
check中的二分可以通过双指针优化到
O
(
n
)
O(n)
O(n),较为经典不做过多赘述。
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define mod 998244353
using namespace std;
string no="no\n",yes="yes\n";
const int inf=1e15;
int n,k;
int a[200005];
int count(int x,int res=0,int sum=0){
for(int l=0,r=0;r<=n;){
if(sum<x){
res+=l;
r++;
sum+=a[r];
}
else{
l++;
sum-=a[l];
}
}
return res;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int l=1,r=1e15;
while(l<=r){
int mid=(l+r)/2;
if(count(mid)<k){
r=mid-1;
}
else{
l=mid+1;
}
}
cout<<r<<endl;
}
void main_init(){}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
cout<<fixed<<setprecision(12);
main_init();
int _=1;
//cin>>_;
while(_--){
solve();
}
}
E.eroengine’s Homework
完全原题,自行洛谷搜索HH的项链,经典扫描线/莫队(本题数据范围放了莫队过)
F.eroengine’s Simple Graph Theory
完全原题。C - 3 Steps (atcoder.jp)
tag:思维题,染色法判断二分图。
G. eroengine’s counting problem
这道题是Permutation Counting的简化版。
tag:树形DP,组合数学。
详细题解待更。
H. eroengine’s Sign Problem
完全原题。C - Dice Sum
tag:背包DP。