20210706集训摸底考
20210706集训摸底考
所用算法
- 贪心+枚举
- 预处理+数学算法
- 字符串排序
- 递归
- 树状数组 未学
目录
T1.桥
T2.【Atcoder_Abc177】C-乘积之和
T3.NAPOR
T4.肉夹馍
T5.异或橙子
题解
T1.桥
题目描述
在数轴上有 n n n 个岛屿,在数轴上依次的位置为 1 , 2... n 1,2...n 1,2...n
相邻的两个岛屿都有一座桥相连,即第 i i i个岛屿与第 i + 1 i+1 i+1 个岛屿之间有桥连接。
现在有 m m m对岛屿之间发生了冲突,为了避免冲突进一步扩大,必须要切断他们之间的联系,即把一些桥给关闭。
现在已经知道哪些岛屿之间发生了冲突,问,最少关闭多少座桥,才能不让冲突进一步扩大。
数据范围
2
≤
n
≤
1
0
5
2\leq n\leq 10^5
2≤n≤105
1
≤
m
≤
1
0
5
1\leq m\leq 10^5
1≤m≤105
2
≤
a
i
<
b
i
≤
1
0
5
2\leq a_i<b_i\leq10^5
2≤ai<bi≤105
输入格式
第一行两个整数 n n n 和 m m m
接下来 m m m 行,每行两个整数 x i x_i xi, y i y_i yi 表示 x i x_i xi 与 y i y_i yi 个岛屿发生了冲突
输出格式
一个整数,表示最少关闭的桥的个数
样例数据
input1
5 2
1 4
2 5
output1
1
input2
9 5
1 8
2 7
3 5
4 6
7 9
output2
2
input3
5 10
1 2
1 3
1 4
1 5
2 3
2 4
2 5
3 4
3 5
4 5
output3
4
反思:这道题其实不难,我一开始将每座桥经过的路径的次数累加,然后从大到小删,就很自闭
正解
这道题用贪心
的思想,将每一座桥按照右端点从小到大排序
定义 r r r为当前的最右点,若当前路的左端点大于等于 r r r,则将该路的右端点的值赋给 r r r, a n s ans ans++
下面是核心代码:
int r=0;
for(int i=1;i<=m;i++)
{
if(e[i].x>=r)
{
r=e[i].y;
ans++;
}
}
T2.【Atcoder_Abc177】C-乘积之和
题目描述
有
N
N
N 个整数
A
i
,
.
.
.
,
A
N
A_i,...,A_N
Ai,...,AN
求
A
i
×
A
j
A_i×A_j
Ai×Aj 的和对
1
0
9
10^9
109+
7
7
7 取模的结果,其中
(
i
,
j
)
(i,j)
(i,j) 满足
1
≤
i
≤
j
≤
N
1\leq i\leq j\leq N
1≤i≤j≤N
数据范围
2
≤
N
≤
2
×
1
0
5
2\leq N\leq 2×10^5
2≤N≤2×105
0
≤
A
i
≤
1
0
9
0\leq A_i\leq 10^9
0≤Ai≤109
输入格式
第一行一个整数
N
N
N,接下来一行
N
N
N 个由空格隔开的整数从
A
1
A_1
A1 从
A
N
A_N
AN
N
N
N
A
1
.
.
.
A
N
A_1...A_N
A1...AN
输出格式
输出 ∑ i = 1 N − 1 ∑ j = i + 1 N A i A j \sum_{i=1}^{N-1}\sum_{j=i+1}^NA_iA_j ∑i=1N−1∑j=i+1NAiAj 对 1 0 9 10^9 109+7 取模的结果
样例数据
input1
3
1 2 3
output1
11
这里由 1 × 2 + 1 × 3 + 2 × 3 = 11 1×2+1×3+2×3=11 1×2+1×3+2×3=11
input2
4
141421356 17320508 22360679 244949
output2
437235829
反思:这道题只能说是very的水,小学生都会做的题我竟然不会,自闭,我直接两重循环,然后就。。。超时了
正解
根据题目,我们可以推出以下规律:
a × b + a × c = a × ( b + c ) a×b+a×c=a×(b+c) a×b+a×c=a×(b+c)
对,就是乘法分配律
,然后就可以预处理前缀和
,然后根据公式计算即可
核心代码如下:
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
ac[i]=(ac[i-1]+a[i])%mod;
}
for(int i=1;i<=n;i++)
ans=(ans+(ac[n]-ac[i]+mod)%mod*a[i]%mod)%mod;
T3.NAPOR
题目描述
Little Mirko 对数学课不在意,所以老师决定在周末让他做一个乏味的作业。
老师给了他一个包含 n n n 行的文本,仅包含数字和小写字母。 Mirko 必须在文本中找到所有数字,并以不降序的顺序输出。 他还必须省略文本中数字可能包含的任何前导零。
可以通过扫描文本并始终使用可能的最大数来确定这些数,即仅以字母或行首/尾定界。例如:01a2b3456cde478
的最终输出结果是1, 2, 478, 3456
。
由于 Mirko 解决问题的速度像蜗牛一样慢,因此他要求您为他编写一个程序以快速解决任务,以便他可以尽快与 Slavko 一起玩。
数据范围
1
≤
n
≤
100
1\leq n\leq 100
1≤n≤100
每个字符串长度不超过
100
100
100,文本中包含数的个数不超过
500500
500500
500500,输入中仅包含小写字母与数字。
输入格式
第一行一个整数
N
N
N,接下来一行
N
N
N 个由空格隔开的整数从
A
1
A_1
A1从
A
N
A_N
AN
N
N
N
A
1
.
.
.
A
N
A_1...A_N
A1...AN
输出格式
输出 ∑ i = 1 N − 1 ∑ j = i + 1 N A i A j \sum_{i=1}^{N-1}\sum_{j=i+1}^NA_iA_j ∑i=1N−1∑j=i+1NAiAj 对 1 0 9 10^9 109+7 取模的结果
样例数据
input1
2
lo3za4
01
output1
1
3
4
input2
4
43silos0
zita002
le2sim
231233
output2
0
2
2
43
231233
input3
4
01bond
02james007
03bond
04austinpowers000
output3
0
1
2
3
4
7
反思:这道题相较于前两道题,难度提高,细节较多,最需要关注的点是字符串的bool比较大小
正解
这里只放字符串的
b
o
o
l
bool
bool 比较函数
bool my(node a,node b)
{
return a.s.size()<b.s.size()||(a.s.size()==b.s.size()&&a.s<b.s);
}
T4.肉夹馍
题目描述
Farmer John 要给奶牛们制作一个超级大的肉夹馍。我们知道,肉夹馍是饼夹肉。
这个肉夹馍一共有 L L L 层。
第 0 0 0 级的肉夹馍只有一层肉馅。
第 1 1 1 级的肉夹馍是上下两层饼,中间有 3 3 3 种肉馅。我们用 B P P P B B P P P B BPPPBBPPPB BPPPBBPPPB,这里 B B B 表示饼, P P P 表示肉馅。
第 2 2 2 级的肉夹馍是 2 2 2 层饼,中间额外一层肉馅,饼与肉馅之间各自夹了一个第 1 1 1 级的肉夹馍。可以看做 B B B B P P P B BPPPB BPPPB P P P B P P P B BPPPB BPPPB B B B
第三级类似, 2 2 2 层饼,中间额外一层肉馅,饼与肉馅之间各自夹了一个第 2 2 2 级的肉夹馍。
第
0
,
1
,
2
0,1,2
0,1,2 层肉夹馍的形状如下图所示:
现在给定一个迭代到第
N
N
N 级的肉夹馍,现在Bessie想知道,从顶部第
1
1
1 层到第
K
K
K 层,一共有多少层的肉馅。
数据范围
1
≤
N
≤
50
1\leq N\leq 50
1≤N≤50
1
≤
K
≤
N
1\leq K\leq N
1≤K≤N级肉夹馍的总层数
输入格式
两个整数 N N N 和 K K K
输出格式
一个整数,表示肉馅的层数
样例数据
input1
2 7
output1
4
input2
1 1
output2
0
input3
50 4321098765432109
output3
2160549382716056
反思:这是一道递归题,将大的肉夹馍转换为小的,然后进行运算
正解
在递归
前,要预处理每一级肉夹馍肉的总数和总层数
P[0]=1,S[0]=1;
for (LL i=1;i<=n;i++)
{
P[i]=P[i-1]*2+1;
//P[i]为第i级肉夹馍的肉的总数
S[i]=S[i-1]*2+3;
//S[i]为第i级肉夹馍的总层数
}
然后,推递归
定义
x
x
x 为第
x
x
x 级肉夹馍,
y
y
y 为当前要求前
y
y
y 层
若 y > S [ x − 1 ] + 2 y>S[x-1]+2 y>S[x−1]+2 时,说明此时要求的层数 y y y 在第 x x x 个汉堡的上半层,则返回 P [ x − 1 ] + 1 + d f s ( x − 1 , y − S [ x − 1 ] − 2 ) P[x-1]+1+dfs(x-1,y-S[x-1]-2) P[x−1]+1+dfs(x−1,y−S[x−1]−2);
若 y = S [ x − 1 ] + 2 y=S[x-1]+2 y=S[x−1]+2 时,说明此时 y y y 正好处于第 x x x 个肉夹馍的中间,则返回 P [ x − 1 ] + 1 P[x-1]+1 P[x−1]+1;
若 y < S [ x − 1 ] + 2 y<S[x-1]+2 y<S[x−1]+2 时,说明此时 y y y 处于第 x x x 个肉夹馍的下半层,则返回 d f s ( x − 1 , y − 1 ) dfs(x-1,y-1) dfs(x−1,y−1);
代码如下:
LL dfs(LL x,LL y)
{
if (x==0) return 1;
if (y<=1) return 0;
if (y>S[x-1]+2) return P[x-1]+1+dfs(x-1,y-S[x-1]-2);
if (y==S[x-1]+2) return P[x-1]+1;
if (y<S[x-1]+2) return dfs(x-1,y-1);
}
T5.异或橙子
题目描述
Janez 喜欢橙子!他制造了一个橙子扫描仪,但是这个扫描仪对于扫描的每个橙子的图像只能输出一个 32 32 32 位整数。
他一共扫描了 n n n 个橙子,但有时他也会重新扫描一个橙子,导致这个橙子的 32 32 32 位整数发生更新。
Janez 想要分析这些橙子,他觉得异或操作非常有趣,他每次选取一个区间从 l l l 至 u u u,他想要得到这个区间内所有子区间的异或和的异或和。
例如 l = 2 , u = 4 l=2,u=4 l=2,u=4 的情况,记橙子序列 A A A 中第 i i i 个橙子的整数是 ,那么他要求的就是:
a 2 ⨁ a 3 ⨁ a 4 ⨁ ( a 2 ⨁ a 3 ) ⨁ ( a 3 ⨁ a 4 ) ⨁ ( a 2 ⨁ a 3 ⨁ a 4 ) a_2 \bigoplus a_3 \bigoplus a_4 \bigoplus (a_2 \bigoplus a_3) \bigoplus (a_3 \bigoplus a_4) \bigoplus (a_2 \bigoplus a_3 \bigoplus a_4) a2⨁a3⨁a4⨁(a2⨁a3)⨁(a3⨁a4)⨁(a2⨁a3⨁a4)
注:式子中的 ⊕ ⊕ ⊕⊕ ⊕⊕ 代表按位异或运算。异或的运算规则如下。 对于两个数的第 i i i 位,记为 x , y x,y x,y,那么:
x
x
x
y
y
y
x
⊕
y
x⊕y
x⊕y
0
0
0
1
1
1
1
1
1
1
1
1
0
0
0
1
1
1
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
0
0
0
例:
13
⨁
23
=
26
13\bigoplus 23=26
13⨁23=26
13
=
0...001101
13=0...001101
13=0...001101
23
=
0...010111
23=0...010111
23=0...010111
13
⨁
23
=
0...011010
13\bigoplus 23=0...011010
13⨁23=0...011010
数据范围
0 ≤ a i ≤ 1 0 9 0\leq a_i\leq 10^9 0≤ai≤109, 1 ≤ n , p ≤ 2 × 1 0 5 1\leq n,p\leq 2×10^5 1≤n,p≤2×105
输入格式
第一行输入两个正整数 n , q n,q n,q,表示橙子数量和操作次数。
接下来一行 n n n 个非负整数,表示每个橙子扫描得到的数值 ,从 1 1 1 开始编号。
接下来 q q q 行,每行三个数:
-
如果第一个数是 1 1 1,接下来输入一个正整数 i i i 与非负整数 j j j,表示将第 i i i 个橙子的扫描值 a i a_i ai 修改为 j j j。
-
如果第一个数是 2 2 2,接下来输入两个正整数 u , l u,l u,l 表示询问这个区间的答案。
输出格式
对于每组询问,输出一行一个非负整数,表示所求的总异或和。
样例数据
input1
3 3
1 2 3
2 1 3
1 1 3
2 1 3
output1
2
0
样例 1 解释
- 最初,A=[1,2,3]A=[1,2,3],询问结果为 1 ⊕ 2 ⊕ 3 ⊕ ( 1 ⊕ 2 ) ⊕ ( 2 ⊕ 3 ) ⊕ ( 1 ⊕ 2 ⊕ 3 ) = 21 ⊕ 2 ⊕ 3 ⊕ ( 1 ⊕ 2 ) ⊕ ( 2 ⊕ 3 ) ⊕ ( 1 ⊕ 2 ⊕ 3 ) = 2 1⊕2⊕3⊕(1⊕2)⊕(2⊕3)⊕(1⊕2⊕3)=21⊕2⊕3⊕(1⊕2)⊕(2⊕3)⊕(1⊕2⊕3)=2 1⊕2⊕3⊕(1⊕2)⊕(2⊕3)⊕(1⊕2⊕3)=21⊕2⊕3⊕(1⊕2)⊕(2⊕3)⊕(1⊕2⊕3)=2
- 修改后,第一个位置被修改为 33 ,询问的结果是 3 ⊕ 2 ⊕ 3 ⊕ ( 3 ⊕ 2 ) ⊕ ( 2 ⊕ 3 ) ⊕ ( 3 ⊕ 2 ⊕ 3 ) = 03 ⊕ 2 ⊕ 3 ⊕ ( 3 ⊕ 2 ) ⊕ ( 2 ⊕ 3 ) ⊕ ( 3 ⊕ 2 ⊕ 3 ) = 0 3⊕2⊕3⊕(3⊕2)⊕(2⊕3)⊕(3⊕2⊕3)=03⊕2⊕3⊕(3⊕2)⊕(2⊕3)⊕(3⊕2⊕3)=0 3⊕2⊕3⊕(3⊕2)⊕(2⊕3)⊕(3⊕2⊕3)=03⊕2⊕3⊕(3⊕2)⊕(2⊕3)⊕(3⊕2⊕3)=0。
input2
5 6
1 2 3 4 5
2 1 3
1 1 3
2 1 5
2 4 4
1 1 1
2 4 4
output2
2
5
4
4
这道题要用到树状数组
,树状数组
如下图:
树状数组基本的用途是维护序列的前缀和对于给定的序列 a a a,我们建立一个数组 c c c,其中 c [ x ] c[x] c[x] 保存序列 a a a 的区间 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [x−lowbit(x)+1,x] 中所有数的和,即 ∑ i = x − l o w b i t ( x ) + 1 x a [ i ] \sum_{i=x-lowbit(x)+1}^{x}a[i] ∑i=x−lowbit(x)+1xa[i]
图中的子节点包括自己,比如
8
8
8 这个节点,里面的值是原始数组中
[
5
,
8
]
[5,8]
[5,8] 的和标记为灰色的节点实际已被上层覆盖,不占据空间
下面是二进制版本,能看到
更新过程是每次加了个二进制的低位 1 ( 101 + 1 − > 110 , 110 + 10 − > 1000 , 1000 + 1000 − > 10000 ) 1(101+1 ->110, 110 + 10 -> 1000, 1000 + 1000 -> 10000) 1(101+1−>110,110+10−>1000,1000+1000−>10000)
查询过程每次就是去掉了二进制中的低位
1
(
1111
−
1
−
>
1110
,
1110
−
10
−
>
1100
,
1100
−
100
−
>
1000
)
1(1111 - 1 -> 1110, 1110 - 10 -> 1100, 1100 - 100 -> 1000)
1(1111−1−>1110,1110−10−>1100,1100−100−>1000)
我们知道,对于一个数的负数就等于对这个数取反
+
1
+1
+1
以二进制数 11010 11010 11010 为例: 11010 11010 11010 的补码为 00101 00101 00101,加 1 1 1 后为 00110 00110 00110,两者相与便是最低位的 1 1 1
其实很好理解,补码和原码必然相反,所以原码有 0 0 0 的部位补码全是 1 1 1,补码再 + 1 +1 +1 之后由于进位那么最末尾的 1 1 1 和原码
最右边的 1 1 1 一定是同一个位置(当遇到第一个 1 1 1 的时候补码此位为 0 0 0,由于前面会进一位,所以此位会变为 1 1 1 )
所以我们只需要进行 KaTeX parse error: Expected 'EOF', got '&' at position 2: a&̲(-a) 就可以取出最低位的 1 1 1 了
会了 l o w b i t lowbit lowbit,我们就可以进行区间查询和单点更新了!!!
单点更新:
继续看开始给出的图
此时如果我们要更改A[1]
则有以下需要进行同步更新
1(001) C[1]+=A[1]
lowbit(1)=001 1+lowbit(1)=2(010) C[2]+=A[1]
lowbit(2)=010 2+lowbit(2)=4(100) C[4]+=A[1]
lowbit(4)=100 4+lowbit(4)=8(1000) C[8]+=A[1]
换成代码就是:
void update(int x,int y,int n)
{
for(int i=x;i<=n;i+=lowbit(i)) //x为更新的位置,y为更新后的数,n为数组最大值
c[i] += y;
}
区间查询:
举个例子 i = 5 i=5 i=5
C [ 4 ] = A [ 1 ] + A [ 2 ] + A [ 3 ] + A [ 4 ] C[4]=A[1]+A[2]+A[3]+A[4] C[4]=A[1]+A[2]+A[3]+A[4];
C [ 5 ] = A [ 5 ] C[5]=A[5] C[5]=A[5];
可以推出: s u m ( i = 5 ) = = > C [ 4 ] + C [ 5 ] sum(i = 5) ==> C[4]+C[5] sum(i=5)==>C[4]+C[5];
序号写为二进制: s u m ( 101 ) = C [ ( 100 ) ] + C [ ( 101 ) ] sum(101)=C[(100)]+C[(101)] sum(101)=C[(100)]+C[(101)];
第一次 101 101 101,减去最低位的 1 1 1 就是 100 100 100;
其实也就是单点更新的逆操作
代码如下:
int getsum(int x){
int ans = 0;
for(int i=x;i;i-=lowbit(i))
ans += c[i];
return ans;
}
以上内容转载自 b e s t s o r t bestsort bestsort 大佬的博客
下面来看这道题,其实就是将模板中的 + + + 号换为 ⨁ \bigoplus ⨁ 即可