2021-07-06

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 2n105
1 ≤ m ≤ 1 0 5 1\leq m\leq 10^5 1m105
2 ≤ a i < b i ≤ 1 0 5 2\leq a_i<b_i\leq10^5 2ai<bi105

输入格式

第一行两个整数 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 1ijN

数据范围

2 ≤ N ≤ 2 × 1 0 5 2\leq N\leq 2×10^5 2N2×105
0 ≤ A i ≤ 1 0 9 0\leq A_i\leq 10^9 0Ai109

输入格式

第一行一个整数 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=1N1j=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 1n100
每个字符串长度不超过 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=1N1j=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 1N50
1 ≤ K ≤ N 1\leq K\leq N 1KN级肉夹馍的总层数

输入格式

两个整数 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[x1]+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[x1]+1+dfs(x1,yS[x1]2);

y = S [ x − 1 ] + 2 y=S[x-1]+2 y=S[x1]+2 时,说明此时 y y y 正好处于第 x x x 个肉夹馍的中间,则返回 P [ x − 1 ] + 1 P[x-1]+1 P[x1]+1;

y < S [ x − 1 ] + 2 y<S[x-1]+2 y<S[x1]+2 时,说明此时 y y y 处于第 x x x 个肉夹馍的下半层,则返回 d f s ( x − 1 , y − 1 ) dfs(x-1,y-1) dfs(x1,y1);

代码如下:

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) a2a3a4(a2a3)(a3a4)(a2a3a4)

:式子中的 ⊕ ⊕ ⊕⊕ 代表按位异或运算。异或的运算规则如下。 对于两个数的第 i i i 位,记为 x , y x,y x,y,那么:

x x x y y y x ⊕ y x⊕y xy
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 1323=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 1323=0...011010

数据范围

0 ≤ a i ≤ 1 0 9 0\leq a_i\leq 10^9 0ai109, 1 ≤ n , p ≤ 2 × 1 0 5 1\leq n,p\leq 2×10^5 1n,p2×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 123(12)(23)(123)=2123(12)(23)(123)=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 323(32)(23)(323)=0323(32)(23)(323)=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] [xlowbit(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=xlowbit(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(11111>1110,111010>1100,1100100>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 即可

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值