Misaka-xxw的博客

昨日种种,皆成今我

环境安装

CUDA

python

这里我装的是python3.9,因为装python3.7的话cuda似乎没有对应版本。新建一个miniconda环境。

pytorch

查找和cuda相对应的版本号,例如Cuda 12.8

1
pip install --pre torch torchvision  --index-url https://download.pytorch.org/whl/nightly/cu128

1
2
3
python font2img.py --src_font=src.otf --dst_font=trg.otf --charset=CN --sample_count=1000 --sample_dir=dir --label=0 --filter --shuffle --mode=font2font
python package.py --dir=dir --save_dir=experiment/data --split_ratio=0.8
python train.py --experiment_dir=experiment --gpu_ids=cuda:0 --batch_size=32 --epoch=100 --sample_steps=200 --checkpoint_steps=500

单调栈

模板

原题
题意:对于数列,中每一个数,输出这个数后面第一个大于它的下标
思路:栈内从大往小放,当栈顶的数小于当前数时,更新栈顶数的answer并弹出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <bits/stdc++.h>
using namespace std;
const int N = 3e6 + 5;
using ll = long long;
int n, p = 0;
int a[N], st[N], f[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
for (; p && a[st[p - 1]] < a[i]; --p)
{
f[st[p - 1]] = i;
}
st[p++] = i;
}
for (int i = 1; i <= n; i++)
cout << f[i] << ' ';
return 0;
}

经典题:接雨水

原题

一个单调递减的单调栈,能找到每个柱子左边和右边的第一个比它高的柱子,从而计算出可以接住的雨水量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def trap(self, height: List[int]) -> int:
st=[]
ans=0
for r,rh in enumerate(height):
while st and height[st[-1]]<rh:
bottom=st.pop()
if not st:
break
l=st[-1]
w=r-l-1
h=min(height[l],height[r])-height[bottom]
ans+=w*h
st.append(r)
return ans

滑动窗口/单调队列

模板

原题
题意:滑动窗口的最小值和最大值

思路:
有更好的新员工,立刻淘汰老员工;
老员工过期了,多好也要淘汰;
新员工永远来到队伍的最底端,队列从头到尾,是从又优秀又老的Top员工,到又普通又新的Back员工。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, k, a[N];
deque<int> maxq, minq;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> k;
for (int i = 0; i < n; i++)
cin >> a[i];
// 区间最小
for (int i = 0; i < k; i++)
{
while (!minq.empty() && minq.front() <= i - k)
minq.pop_front();
while (!minq.empty() && a[minq.back()] >= a[i])
minq.pop_back();
minq.push_back(i);
}
cout << a[minq.front()];
for (int i = k; i < n; i++)
{
while (!minq.empty() && minq.front() <= i - k)
minq.pop_front();
while (!minq.empty() && a[minq.back()] >= a[i])
minq.pop_back();
minq.push_back(i);
cout << ' ' << a[minq.front()];
}
cout << endl;

// 区间最大
for (int i = 0; i < k; i++)
{
while (!maxq.empty() && maxq.front() <= i - k)
maxq.pop_front();
while (!maxq.empty() && a[maxq.back()] <= a[i])
maxq.pop_back();
maxq.push_back(i);
}
cout << a[maxq.front()];
for (int i = k; i < n; i++)
{
while (!maxq.empty() && maxq.front() <= i - k)
maxq.pop_front();
while (!maxq.empty() && a[maxq.back()] <= a[i])
maxq.pop_back();
maxq.push_back(i);
cout << ' ' << a[maxq.front()];
}
cout << endl;
return 0;
}

经典题:无重复字符的最长子串

原题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
const set=new Set();
let j=0,ans=0,n=s.length;
for(i=0;i<n;i++){
while(j<n&&!set.has(s[j])){
set.add(s[j]);
j++;
}
ans=Math.max(ans,j-i);
set.delete(s[i]);
}
return ans;
};

https://leetcode.cn/problems/find-all-anagrams-in-a-string?envType=study-plan-v2&envId=top-100-liked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Solution {
public IList<int> FindAnagrams(string s, string p) {
var ans=new List<int>();
int sl=s.Length,pl=p.Length;
if(sl<pl){
return ans;
}
int[] cnt=new int[26];
int dif=0;
for(int i=0;i<pl;i++){
++cnt[s[i]-'a'];
--cnt[p[i]-'a'];
}
for(int i=0;i<26;i++){
if(cnt[i]!=0) ++dif;
}
if(dif==0) ans.Add(0);
for(int i=0;i<sl-pl;i++){
int idx=s[i]-'a';
if(cnt[idx]==1) --dif;
else if(cnt[idx]==0) ++dif;
--cnt[idx];

idx=s[i+pl]-'a';
if(cnt[idx]==-1) --dif;
else if(cnt[idx]==0) ++dif;
++cnt[idx];

if(dif==0) ans.Add(i+1);
}
return ans;
}
}

2023的题还需要烧烤一下/(ㄒoㄒ)/~~
所以先做2022的题
暂未完成:最长上升子序列


P8770 [蓝桥杯 2022 省 A] 填空问题

题目描述

试题 A :裁纸刀

【问题描述】

小蓝有一个裁纸刃,每次可以将一张纸沿一条直线裁成两半。

小蓝用一张纸打印出两行三列共 个二维码,至少使用九次裁出来,下图给出了一种裁法。

在上面的例子中,小蓝的打印机没办法打印到边缘,所以边缘至少要裁 次。另外,小蓝每次只能裁一张纸,不能重叠或者拼起来裁。

如果小蓝要用一张纸打印出 列共 个二维码,他至少需要裁多少次?

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

试题 B:灭鼠先锋

【问题描述】

灭鼠先锋是一个老少咸宜的棋盘小游戏,由两人参与,轮流操作。

灭鼠先锋的棋盘有各种规格,本题中游戏在两行四列的棋盘上进行。游戏的规则为:两人轮流操作,每次可选择在棋盘的一个空位上放置一个棋子,或在同一行的连续两个空位上各放置一个棋子,放下棋子后使棋盘放满的一方输掉游戏。

小蓝和小乔一起玩游戏,小蓝先手,小乔后手。小蓝可以放置棋子的方法很多,通过旋转和翻转可以对应如下四种情况:

1
2
X000 XX00 OXOO OXXO
0000 0000 0000 0000

其中 O 表示棋盘上的一个方格为空,X 表示该方格已经放置了棋子。

请问,对于以上四种情况,如果小蓝和小乔都是按照对自己最优的策略来玩游戏,小蓝是否能获胜。如果获胜,请用 V 表示,否则用 L 表示。请将四种情况的胜负结果按顺序连接在一起提交。

解法

裁纸刀

灭鼠先锋

博弈论

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits\stdc++.h>
using namespace std;
char board[5];
bool dfs()
{
if (strcmp(board, "XXXX") == 0)
{
return false;
}
for (int i = 0; i < 4; i++)
{
if (board[i] == 'O')
{
board[i] = 'X';
if (!dfs())
{
board[i] = 'O';
return true;
}
if (board[i + 1] == 'O')
{
board[i + 1] = 'X';
if (!dfs())
{
board[i] = 'O';
board[i + 1] = 'O';
return true;
}
board[i + 1] = 'O';
}
board[i] = 'O';
}
}
}
int main()
{
char con[][5] = {"XOOO", "XXOO", "OXOO", "OXXO"};
for (int i = 0; i < 4; i++)
{
strcpy(board, con[i]);
if (!dfs())//you fail
putchar('V');
else
putchar('L');
}
return 0;
}

[蓝桥杯 2022 省 A] 求和

题目描述

给定 个整数 , 求它们两两相乘再相加的和,即

输入格式

输入的第一行包含一个整数

第二行包含 个整数

输出格式

输出一个整数 ,表示所求的和。请使用合适的数据类型进行运算。

样例 #1

样例输入 #1

1
2
4
1 3 6 9

样例输出 #1

1
117

提示

对于 的数据,

对于所有评测用例,

蓝桥杯 2022 省赛 A 组 C 题。

解法

后缀和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
using ll=unsigned long long;
const int N=(2e5)+5;
using namespace std;
int n;
int a[N];
ll sum=0;
ll ans=0;
int main() {
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
for(int i=n-1;i>=0;i--){
ans+=a[i]*sum;
sum+=a[i];
}
printf("%lld",ans);
return 0;
}


[蓝桥杯 2022 省 A] 选数异或

题目描述

给定一个长度为 的数列 和一个非负整数 , 给定 次查询, 每次询问能否从某个区间 中选择两个数使得他们的异或等于

输入格式

输入的第一行包含三个整数

第二行包含 个整数

接下来 行,每行包含两个整数 表示询问区间

输出格式

对于每个询问, 如果该区间内存在两个数的异或为 则输出 yes, 否则输出 no

样例 #1

样例输入 #1

1
2
3
4
5
6
4 4 1
1 2 3 4
1 4
1 2
2 3
3 3

样例输出 #1

1
2
3
4
yes
no
yes
no

提示

【样例说明】

显然整个数列中只有 2,3 的异或为 1 。

【评测用例规模与约定】

对于 的评测用例, ;

对于 的评测用例, ;

对于所有评测用例,

蓝桥杯 2022 省赛 A 组 D 题。

解法

异或:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
const int N=(1e5)+5;
using namespace std;
int n,m,x;
unordered_map<int,int> mp;//数值,对应位置
int f[N];//对于查询[l,r],f[r]表示[l,r]要有相配答案,l的最右
int main() {
scanf("%d%d%d",&n,&m,&x);
int a;
for(int i=1;i<=n;i++){
scanf("%d",&a);
f[i]=max(f[i-1],mp[a^x]);//找到达到要求的最左边界
mp[a]=i;
}
int l,r;
while(m--){
scanf("%d%d",&l,&r);
if(l<=f[r])
printf("yes\n");
else
printf("no\n");
}
return 0;
}


[蓝桥杯 2022 省 A] 爬树的甲壳虫

题目描述

有一只甲壳虫想要爬上一颗高度为 的树,它一开始位于树根, 高度为 ,当它尝试从高度 爬到高度为 的位置时有 的概率会掉回树根, 求它从树根爬到树顶时, 经过的时间的期望值是多少。

输入格式

输入第一行包含一个整数 表示树的高度。

接下来 行每行包含两个整数 , 用一个空格分隔,表示

输出格式

输出一行包含一个整数表示答案,答案是一个有理数,请输出答案对质数 取模的结果。其中有理数 对质数 取模的结果是整数 满足

样例 #1

样例输入 #1

1
2
1
1 2

样例输出 #1

1
2

样例 #2

样例输入 #2

1
2
3
4
3
1 2
3 5
7 11

样例输出 #2

1
623902744

提示

对于 的评测用例, ;

对于 的评测用例, ;

对于所有评测用例,

蓝桥杯 2022 省赛 A 组 E 题。

bgm🐌🎵

是🐞不是🐌喂

解法

状态转移

设从高度爬到高度的期望时间是.


转移方程:

费马小定理的一种表述:对于任意整数和质数,有

模运算和逆元

在模运算中,使用逆元将除法转换为乘法,避免了直接计算分数。
快速幂模板
乘法逆元模板

逆元的定义:如果存在一个整数 ,使得 ,那么 就是 在模 下的逆元

费马小定理的一种表述:如果是一个质数,且 互质,那么:
另一种表述:对于任意整数和质数,有

计算 在模下的值。根据费马小定理得到:

对于模数,它是一个质数,所以可以直接用 来计算

快速幂函数

函数quick_pow用于计算 。它利用了快速幂算法,时间复杂度为
通过不断将指数右移(相当于除以2),并根据当前指数的奇偶性决定是否将当前的乘到结果中。
将分数转换为模运算下的乘法。

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using ll=unsigned long long;
const int p=998244353;//取模
using namespace std;
int n;
ll x,y,f;
ll quick_pow(ll a){
ll res=1;
ll b=p-2;
while(b){
if(b&1){
res=res*a%p;
}
a=a*a%p;
b>>=1;
}
return res;
}
int main() {
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%lld%lld",&x,&y);
f=((f+1)*y%p)*quick_pow(y-x)%p;
}
cout<<f<<endl;
return 0;
}

时间复杂度
每次计算逆元的时间复杂度为
总时间复杂度为 ,其中 n 是树的高度。


[蓝桥杯 2022 省 A] 青蛙过河

题目描述

小青蛙住在一条河边,它想到河对岸的学校去学习。小青蛙打算经过河里的石头跳到对岸。

河里的石头排成了一条直线,小青蛙每次跳跃必须落在一块石头或者岸上。不过,每块石头有一个高度,每次小青蛙从一块石头起跳,这块石头的高度就会下降 ,当石头的高度下降到 时小青蛙不能再跳到这块石头上(某次跳跃后使石头高度下降到 是允许的)。

小青蛙一共需要去学校上 天课,所以它需要往返 次。当小青蛙具有一个跳跃能力 时,它能跳不超过 的距离。

请问小青蛙的跳跃能力至少是多少才能用这些石头上完 次课。

输入格式

输入的第一行包含两个整数 , 分别表示河的宽度和小青蛙需要去学校的天数。请注意 才是实际过河的次数。

第二行包含 个非负整数 , 其中 表示在河中与 小青蛙的家相距 的地方有一块高度为 的石头, 表示这个位置没有石头。

输出格式

输出一行, 包含一个整数, 表示小青蛙需要的最低跳跃能力。

样例 #1

样例输入 #1

1
2
5 1
1 0 1 0

样例输出 #1

1
4

提示

【样例解释】

由于只有两块高度为 的石头,所以往返只能各用一块。第 块石头和对岸的距离为 ,如果小青蛙的跳跃能力为 则无法满足要求。所以小青蛙最少需要 的跳跃能力。

【评测用例规模与约定】

对于 的评测用例,;

对于 的评测用例,;

对于所有评测用例,

蓝桥杯 2022 省赛 A 组 F 题。

bgm🐸🎵

解法

贪心+前缀和
对于每个起点,检查区间 内的石头总高度是否足够支持 次跳跃。
如果某个区间的石头总高度不足,则增加,扩大区间范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;
using ll=unsigned long long;
const int N=(1e5)+5;
int n,x;//宽度和小青蛙需要去学校的天数。
ll sum[N];//石头总数前缀和
int main() {
scanf("%d%d",&n,&x);
x<<=1;//实际上让青蛙2x次过岸
int h;//石头高度
for(int i=1;i<n;i++)
{
scanf("%d",&h);
sum[i]=sum[i-1]+h;//[l,r]区间的石头总高度为sum[r]-sum[l-1]
}
int y=1;//跳跃能力
sum[n]=INT_MAX;//到对岸相当于有无数的石头
for(int i=0;i<n-y;i++)
while(sum[i+y]-sum[i]<x)//[i+1,i+y]区间不够2x只青蛙落脚
y++;
printf("%d",y);
return 0;
}

[蓝桥杯 2022 省 A] 最长不下降子序列

题目描述

给定一个长度为 的整数序列:。现在你有一次机会,将其中连续的 个数修改成任意一个相同值。请你计算如何修改可以使修改后的数列的最长不下降子序列最长,请输出这个最长的长度。

最长不下降子序列是指序列中的一个子序列,子序列中的每个数不小于在它之前的数。

输入格式

输入第一行包含两个整数

第二行包含 个整数

输出格式

输出一行包含一个整数表示答案。

样例 #1

样例输入 #1

1
2
5 1
1 4 2 8 5

样例输出 #1

1
4

提示

对于 的评测用例, ;

对于 的评测用例, ;

对于 的评测用例, ;

对于所有评测用例,

蓝桥杯 2022 省赛 A 组 G 题。

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
```

---

# [蓝桥杯 2022 省 A] 扫描游戏

## 题目描述

有一根围绕原点 $O$ 顺时针旋转的棒 $OA$,初始时指向正上方(Y 轴正向)。平面中有若干物件,第 $i$ 个物件的坐标为 $\left(x_{i}, y_{i}\right)$,价值为 $z_{i}$。当棒扫到某个物件时,棒的长度会瞬间增长 $z_{i}$,且物件瞬间消失(棒的顶端恰好碰到物件也视为扫到),如果此时增长完的棒又额外碰到了其他物件,也按上述方式消去(它和上述那个点视为同时消失)。

如果将物件按照消失的时间排序,则每个物件有一个排名,同时消失的物件排名相同,请输出每个物件的排名,如果物件永远不会消失则输出 $-1$。

## 输入格式

输入第一行包含两个整数 $n$、$L$,用一个空格分隔,分别表示物件数量和棒的初始长度。

接下来 $n$ 行每行包含第三个整数 $x_{i}, y_{i}, z_{i}$。

注意存在 $(x_{i}, y_{i})=(0,0)$ 的情况,这些点视为一开始就立刻被碰到。

## 输出格式

输出一行包含 $n$ 整数,相邻两个整数间用一个空格分隔,依次表示每个物件的排名。

## 样例 #1

### 样例输入 #1

5 2
0 1 1
0 3 2
4 3 5
6 8 1
-51 -33 2

1
2
3

### 样例输出 #1


1 1 3 4 -1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

## 提示

对于 $30 \%$ 的评测用例,$1 \leq n \leq 500$ ;

对于 $60 \%$ 的评测用例,$1 \leq n \leq 5000$;

对于所有评测用例,$1 \leq n \leq 2\times10^5,-10^{9} \leq x_{i}, y_{i} \leq 10^{9}, 1 \leq L, z_{i} \leq 10^{9}$ 。

样蓝桥杯 2022 省赛 A 组 H 题。

## 题解

为什么不试试优先队列呢?
此处建议用斜率代替角度,角度每个网站的数据要求精度不一样。但我过了洛谷和蓝桥杯官网就懒得改了```(*ꈍ◡ꈍ*)```

```cpp
#include <bits/stdc++.h>
using ll = long long;
using ull = unsigned long long;
const int N = (2e5) + 5;
const double pi2 = 2 * M_PI; // 2*圆周率pi
using namespace std;

struct point {
int id; // 序号
int z; // 碰到增加的值
ull r; // 半径的平方
double angle; // 角度(以从y轴正方向开始的顺时针角度表示)
bool operator>(const point &other) const {
return angle > other.angle; // 优先队列按角度排序
}
} p[N];

int main() {
int n;//棒的个数
ull l;//棒的长度
__int128 l2;//棒的长度的平方,棒的长度的平方。__int128防止超过数据范围
scanf("%d%lld", &n, &l);
l2 = l * l;
ll x, y;
for (int i = 0; i < n; i++) {
scanf("%lld%lld%d", &x, &y, &p[i].z);
p[i].id = i;
p[i].r = x * x + y * y;//半径的平方
p[i].angle = atan2(x, y);//算角度。atan2可以处理为0的情况,注意是顺时针从y轴正方向开始
if (p[i].angle < 0)//把第二、三象限的角度转过来
p[i].angle = pi2 + p[i].angle;
}
sort(p, p + n, [](const point &a, const point &b) -> bool {
return a.r < b.r;
});//按半径排序
priority_queue<point, vector<point>, greater<>> q; // 按角度排序的优先队列
int pos;
for (pos = 0; pos < n && p[pos].r <= l2; pos++) {
q.push(p[pos]);
}

int cnt = 1, last_cnt = 1;//排名,并列排名
int ans[N];//答案
double rounds = pi2, angle = 0; // rounds 表示本轮结束的角度,angle 保存上一个被扫到的点的角度
memset(ans, -1, sizeof(ans));//初始化为-1
const double eps = 1e-11; // 浮点数比较阈值。1e-9官网过不了

while (!q.empty()) {
point u = q.top();
q.pop();
if (u.angle > rounds + eps) // 如果当前点的角度超过本轮结束的角度
rounds += pi2;
if (fabs(u.angle - angle) < eps) { // 判断两角度是否“相等”,用并列排名
ans[u.id] = last_cnt;
} else {
last_cnt=ans[u.id]=cnt;//更新并列的排名
angle=u.angle;//角度
}
++cnt;//更新排名

l += u.z;
l2 = l * l;
while (pos < n && p[pos].r <= l2) {
// 新点的角度转换到绝对角度,先加上当前轮次的偏移量
p[pos].angle += (rounds - pi2);
if (p[pos].angle < angle - eps) {
p[pos].angle += pi2;
}
q.push(p[pos]);
++pos;
}
}
printf("%d", ans[0]);
for (int i = 1; i < n; i++)
printf(" %d", ans[i]);
return 0;
}


[蓝桥杯 2022 省 A] 数的拆分

题目描述

给定 个正整数 ,分别问每个 能否表示为 的形式,其中 为正整数, 为大于等于 的正整数。

输入格式

输入第一行包含一个整数 表示询问次数。

接下来 行,每行包含一个正整数

输出格式

对于每次询问,如果 能够表示为题目描述的形式则输出 yes,否则输出 no

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
7
2
6
12
4
8
24
72

样例输出 #1

1
2
3
4
5
6
7
no
no
no
yes
yes
no
yes

提示

【样例说明】

个数分别可以表示为:

【评测用例规模与约定】

对于 的评测用例,;

对于 的评测用例,;

对于 的评测用例,;

对于所有评测用例,

蓝桥杯 2022 省赛 A 组 I 题。

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<bits/stdc++.h>
using ll=long long;
const int N=4000;
using namespace std;
int n;
int not_prime[N+4];
vector<int> primes;
inline void init_primes(){
for(int i=2;i<=N;i++){
if(!not_prime[i]){
primes.push_back(i);
}
for(int j:primes){
if(i*j>N)
break;
not_prime[i*j]=true;
if(i%j==0)
break;
}
}
}
inline bool check(ll x){
ll y=pow(x,0.5);
if(y*y==x)
return true;
y=pow(x,1.0/3);
if(y*y*y==x||(y+1)*(y+1)*(y+1)==x)//?
return true;
return false;
}
int main() {
init_primes();
int t;
ll n;
scanf("%d",&t);
while(t--){
scanf("%lld",&n);
if(check(n)){
printf("yes\n");
continue;
}
bool flag=true;
for(int i:primes){
int cnt=0;
while(n%i==0){
n/=i;
cnt++;
}
if(cnt==1){
flag=false;
break;
}
}
if(flag&&check(n)){
printf("yes\n");
}
else{
printf("no\n");
}

}
return 0;
}

[蓝桥杯 2022 省 A] 推导部分和

题目描述

对于一个长度为 的整数数列 ,小蓝想知道下标 的部分和 是多少?

然而,小蓝并不知道数列中每个数的值是多少,他只知道它的 个部分和的值。其中第 个部分和是下标 的部分和 , 值是

输入格式

第一行包含 3 个整数 。分别代表数组长度、已知的部分和数量 和询问的部分和数量。

接下来 行,每行包含 个整数

接下来 行,每行包含 个整数 ,代表一个小蓝想知道的部分和。

输出格式

对于每个询问, 输出一行包含一个整数表示答案。如果答案无法确定, 输出 UNKNOWN

样例 #1

样例输入 #1

1
2
3
4
5
6
7
5 3 3
1 5 15
4 5 9
2 3 5
1 5
1 3
1 2

样例输出 #1

1
2
3
15
6
UNKNOWN

提示

对于 的评测用例,

对于 的评测用例,

对于 的评测用例,

对于 的评测用例,

对于 的评测用例,

对于所有评测用例, , 。数据保证没有矛盾。

蓝桥杯 2022 省赛 A 组 J 题。

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<bits/stdc++.h>

using namespace std;
using ll = long long;
vector<int> pa;
vector<ll> sum;

int find(int x) {
if (x != pa[x]) {
int root = find(pa[x]);
sum[x] += sum[pa[x]];
pa[x] = root;
}
return pa[x];
}

int main() {
int n, m, q;
scanf("%d%d%d", &n, &m, &q);

pa.resize(n + 1);
sum.resize(n + 1, 0);
iota(pa.begin(), pa.end(), 0);
int l, r;
ll s;
while (m--) {
scanf("%d%d%lld", &l, &r, &s);
int root_l = find(l - 1);
int root_r = find(r);
if (root_l != root_r) {
pa[root_r] = root_l;
sum[root_r] = s + sum[l - 1] - sum[r];
}
}
while (q--) {
scanf("%d%d", &l, &r);
if (find(l - 1) == find(r)) {
printf("%lld\n", sum[r] - sum[l - 1]);
} else {
printf("UNKNOWN\n");
}
}
return 0;
}

好几道题还没做。
算了,寒假里做几道放几道呗

[蓝桥杯 2023 省 A] 填空问题

题目描述

A. 幸运数

小蓝认为如果一个数含有偶数个数位,并且前面一半的数位之和等于后面一半的数位之和,则这个数是他的幸运数字。例如 是一个幸运数字,因为它有 个数位,并且 。现在请你帮他计算从 之间共有多少个不同的幸运数字。

B. 有奖问答

小蓝正在参与一个现场问答的节目。活动中一共有 道题目,每题只有答对和答错两种情况,每答对一题得 分,答错一题分数归零。

小蓝可以在任意时刻结束答题并获得目前分数对应的奖项,之后不能再答任何题目。最高奖项需要 分,所以到达 分时小蓝会直接停止答题。

已知小蓝最终实际获得了 分对应的奖项,请问小蓝所有可能的答题情况有多少种?

提示

第十四届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组 A-B

解法

填空题,暴!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
long long ans1,ans2;//4430091,8335366
// 11~99999999
void solve1(){
int cnt[40][5]={0};//和为几(1~36),几位数(1~4)
for(int i=1;i<=9999;i++){
int w=0,s=0,a=i;
while(a){
w++;
s+=a%10;
a/=10;
}
cnt[s][w]++;
}
for(int i=1;i<=36;i++){
for(int j=1;j<=4;j++){
int s=0;
for(int k=1;k<=j;k++){
s+=cnt[i][k];
}
ans1+=cnt[i][j]*s;
}
}
}
//答对,答错或者结束比赛
//300题拿了7分,答对1题加10分,答错分数归零。其中分数不可能>=100
void dfs(int t,int s){//题号,分数
if(s==100)//到达100分时小蓝会直接停止答题
return;
if(s==70){
ans2++;}
if(t==31)//比赛结束
return;
dfs(t+1,s+10);//答对+1
dfs(t+1,0);//答错分数归0
}
void solve2(){
dfs(1,0);
}


[蓝桥杯 2023 省 A] 平方差

题目描述

给定 ,问 中有多少个数 满足存在整数 使得

输入格式

输入一行包含两个整数 ,用一个空格分隔。

输出格式

输出一行包含一个整数满足题目给定条件的 的数量。

样例 #1

样例输入 #1

1
1 5

样例输出 #1

1
4

提示

【样例说明】

【评测用例规模与约定】

对于 的评测用例,

对于所有评测用例,

第十四届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组 C

解法

$$

1
2
3
4
5
6
7
8
9
#include <bits/stdc++.h>
using namespace std;
signed main()
{
int l, r;
cin >> l >> r;
cout << (r >> 1) + (r & 1) - (l >> 1) + (r >> 2) - (l >> 2) + !(l & 3) << endl;
return 0;
}


[蓝桥杯 2023 省 A] 更小的数

题目描述

image

小蓝有一个长度均为 且仅由数字字符 组成的字符串,下标从 ,你可以将其视作是一个具有 位的十进制数字 ,小蓝可以从 中选出一段连续的子串并将子串进行反转,最多反转一次。小蓝想要将选出的子串进行反转后再放入原位置处得到的新的数字 满足条件 ,请你帮他计算下一共有多少种不同的子串选择方案,只要两个子串在 中的位置不完全相同我们就视作是不同的方案。

注意,我们允许前导零的存在,即数字的最高位可以是 ,这是合法的。

输入格式

输入一行包含一个长度为 的字符串表示 (仅包含数字字符 ),从左至右下标依次为

输出格式

输出一行包含一个整数表示答案。

样例 #1

样例输入 #1

1
210102

样例输出 #1

1
8

提示

【样例说明】

一共有 种不同的方案:

  1. 所选择的子串下标为 ,反转后的
  2. 所选择的子串下标为 ,反转后的
  3. 所选择的子串下标为 ,反转后的
  4. 所选择的子串下标为 ,反转后的
  5. 所选择的子串下标为 ,反转后的
  6. 所选择的子串下标为 ,反转后的
  7. 所选择的子串下标为 ,反转后的
  8. 所选择的子串下标为 ,反转后的

【评测用例规模与约定】

对于 的评测用例,

对于 的评测用例,

对于所有评测用例,

解法

1
2
3
4
5
6
对于区间[l,r]
if l!=r:
if num[l]>num[r]:
可以翻转
elif [l+1,r-1]in [l,r] and [_l,_r]可以翻转:
可以翻转

动态规划,根据对称性,优化成一维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>
using namespace std;
#define N 5005
char num[N];
int n;
long long res;
bool f[N * 2];
signed main()
{
scanf("%s", num);
n = strlen(num);
for (register int k = 1; k < n; k++)
{
for (register int i = n - k - 1; i >= 0; i--)//正着也一样
{
int j = i + k;
if (num[i] > num[j])//可以翻转
{
res++;
f[i + j] = true;
}
else if (num[i] == num[j])//看区间[i+1,j-1]是否可以翻转
{
if (f[i + j])
res++;
}
else//不可以翻转
{
f[i+j]=false;
}
}
}
printf("%lld\n", res);
return 0;
}


[蓝桥杯 2023 省 A] 颜色平衡树

题目描述

给定一棵树,结点由 编号,其中结点 是树根。树的每个点有一个颜色

如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。

求出这棵树中有多少个子树是颜色平衡树。

输入格式

输入的第一行包含一个整数 ,表示树的结点数。

接下来 行,每行包含两个整数 ,用一个空格分隔,表示第 个结点的颜色和父亲结点编号。

特别地,输入数据保证 ,也即 号点没有父亲结点。保证输入数据是一棵树。

输出格式

输出一行包含一个整数表示答案。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
6
2 0
2 1
1 2
3 3
3 4
1 4

样例输出 #1

1
4

提示

【样例说明】

编号为 个结点对应的子树为颜色平衡树。

【评测用例规模与约定】

对于 的评测用例,

对于 的评测用例,

对于所有评测用例,

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
```

# [蓝桥杯 2023 省 A] 买瓜

## 题目描述

小蓝正在一个瓜摊上买瓜。瓜摊上共有 $n$ 个瓜,每个瓜的重量为 $A_i$。小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。

小蓝希望买到的瓜的重量的和恰好为 $m$。

请问小蓝至少要劈多少个瓜才能买到重量恰好为 $m$ 的瓜。如果无论怎样小蓝都无法得到总重恰好为 $m$ 的瓜,请输出 $-1$。

## 输入格式

输入的第一行包含两个整数 $n,m$,用一个空格分隔,分别表示瓜的个数和小蓝想买到的瓜的总重量。

第二行包含 $n$ 个整数 $A_i$,相邻整数之间使用一个空格分隔,分别表示每个瓜的重量。

## 输出格式

输出一行包含一个整数表示答案。

## 样例 #1

### 样例输入 #1

3 10
1 3 13

1
2
3

### 样例输出 #1


2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

## 提示

#### 【评测用例规模与约定】

对于 $20 \%$ 的评测用例,$n \leq 10$;

对于 $60 \%$ 的评测用例,$n \leq 20$;

对于所有评测用例,$1 \leq n \leq 30$,$1 \leq A_i \leq 10^9$,$1 \leq m \leq 10^9$。

## 前情提要🍉(bushi)

<iframe src="https://player.bilibili.com/player.html?aid=448136466&bvid=BV14j41117D6&cid=1259813341&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="position: absolute; width: 1000px; left: 0; top: 0;"> </iframe>

## 解法
(瞅了一眼标签,折半搜索?跟生瓜蛋子一样不熟)
西瓜对半切/不切,不妨乘二,当作一个西瓜/两个西瓜来做
先贪心排序,优先找大瓜。
然后深度优先搜索,根据质量上下超限(小于要求质量用后缀和判断)、劈瓜数已大于当前最小答案来剪枝。
```cpp
#include<bits/stdc++.h>
using ll=long long;
const int N=32;
using namespace std;
int n,mid,m;//西瓜总数量,一半数量,要求总质量
int ans=INT_MAX;//答案
int a[N];//有一个人前来买瓜
ll sum[N];//后缀和,用来剪枝
void dfs(int i,int num,int weight){//到哪个瓜了,劈了多少瓜,买了几斤瓜
if(weight>=m){
if(weight==m)
ans=min(ans,num);
return;
}
if(i==n||num>=ans||weight+sum[i]<m)//超总数的递归出口,超过最小劈瓜数递归,质量不足以达到要求数剪枝
return;
dfs(i+1,num,weight+(a[i]<<1));//卖你生瓜蛋子
dfs(i+1,num+1,weight+a[i]);//你**劈我瓜是吧!
dfs(i+1,num,weight);//你要不要吧
}
int main() {
scanf("%d%d",&n,&m);
mid=n/2;
m*=2;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
sort(a,a+n,greater<>());//从大到小贪心
for(int i=n-1;i>=0;i--){
sum[i]=sum[i+1]+(a[i]<<1);//求后缀和
}
dfs(0,0,0);
if(ans==INT_MAX)
ans=-1;
printf("%d\n",ans);
return 0;
}


[蓝桥杯 2023 省 A] 网络稳定性

题目描述

有一个局域网,由 个设备和 条物理连接组成,第 条连接的稳定性为

对于从设备 到设备 的一条经过了若干个物理连接的路径,我们记这条路径的稳定性为其经过所有连接中稳定性最低的那个。

我们记设备 到设备 之间通信的稳定性为 的所有可行路径的稳定性中最高的那一条。

给定局域网中的设备的物理连接情况,求出若干组设备 之间的通信稳定性。如果两台设备之间不存在任何路径,请输出

输入格式

输入的第一行包含三个整数 ,分别表示设备数、物理连接数和询问数。

接下来 行,每行包含三个整数 ,分别表示 之间有一条稳定性为 的物理连接。

接下来 行,每行包含两个整数 ,表示查询 之间的通信稳定性。

输出格式

输出 行,每行包含一个整数依次表示每个询问的答案。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
5 4 3
1 2 5
2 3 6
3 4 1
1 4 3
1 5
2 4
1 3

样例输出 #1

1
2
3
-1
3
5

提示

【评测用例规模与约定】

对于 的评测用例,

对于 的评测用例,

对于所有评测用例,

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
```

---

# [蓝桥杯 2023 省 A] 异或和之和

## 题目描述

给定一个数组 $A_i$,分别求其每个子段的异或和,并求出它们的和。或者说,对于每组满足 $1 \leq L \leq R \leq n$ 的 $L,R$,求出数组中第 $L$ 至第 $R$ 个元素的异或和。然后输出每组 $L,R$ 得到的结果加起来的值。

## 输入格式

输入的第一行包含一个整数 $n$ 。

第二行包含 $n$ 个整数 $A_i$,相邻整数之间使用一个空格分隔。

## 输出格式

输出一行包含一个整数表示答案。

## 样例 #1

### 样例输入 #1

5
1 2 3 4 5

1
2
3

### 样例输出 #1


39
```

提示

【评测用例规模与约定】

对于 的评测用例,

对于 的评测用例,;

对于所有评测用例,

解法

异或的性质


(没什么,复习一下)

设序列为,从的异或和为, 从的异或前缀和为,则

即对~个前缀和,两两异或的和。
按二进制位数相加
```cpp

include

using namespace std;
using ll=long long;
const int N=(1e5)+5;
int n;
int f[N];
ll res=0ll;
int main() {
int a;
scanf(“%d”,&n);
for(int i=1;i<=n;i++){
scanf(“%d”,&a);
f[i]=f[i-1]^a;
}
int bei=1;//位权
for(int i=0;i<=20;i++){//20位二进制
int numof1=0,numof0=1;
ll sum=0;
for(int j=1;j<=n;j++){
if(f[j]&1){//最低一位1^0=1
sum+=numof0;
numof1++;
}else{//最低一位0^1=1
sum+=numof1;
numof0++;
}
f[j]>>=1;//下一位
}
res+=sum*bei;
bei<<=1;//位权翻倍
}
cout<<res<<endl;
return 0;
}
```..

原题与参考:
https://www.luogu.com.cn/problem/P3865
https://oi-wiki.org/ds/sparse-table/

模板题题意

给定个数,有个询问,对于每个询问,你需要回答区间中的最大值

简介

ST 表(Sparse Table,稀疏表)是用于解决可重复贡献问题的数据结构。

可重复贡献问题:对于运算opt,满足,如

比起线段树,只能静态查询,不能修改信息。
使用倍增思想,时间复杂度:

步骤

对数初处理

1
2
3
4
int lg[N];//对数表
for(int i=2;i<=n;i++){//初始化对数表
lg[i]=lg[i>>1]+1;
}

转移方程

为数序列,为区间的最大值,则

这里有张动图来着

1
2
3
4
5
6
7
int w=1;
for(int j=1;j<=lg[n];j++){
for(int i=1;i<=n-w;i++){
f[i][j]=max(f[i][j-1],f[i+w][j-1]);
}
w<<=1;
}

询问

询问时,把区间分成,其中
即查询
这里有张动图来着

1
2
3
scanf("%d%d",&l,&r);
s=lg[r-l+1];
printf("%d\n",max(f[l][s],f[r-(1<<s)+1][s]));

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
const int N=(1e5)+5;
using namespace std;
int n,m;//数列的长度,询问的个数
int f[N][22];//区间[i,i+2^j-1]的最大值
int lg[N];//对数表
int main() {
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++){//初始化对数表
lg[i]=lg[i>>1]+1;
}
for(int i=1;i<=n;i++){
scanf("%d",&f[i][0]);
}
int w=1;
for(int j=1;j<=lg[n];j++){
for(int i=1;i<=n-w;i++){
f[i][j]=max(f[i][j-1],f[i+w][j-1]);
}
w<<=1;
}
int l,r,s;
while(m--){
scanf("%d%d",&l,&r);
s=lg[r-l+1];
printf("%d\n",max(f[l][s],f[r-(1<<s)+1][s]));
}
return 0;
}

【模板】并查集

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 ,表示共有 个元素和 个操作。

接下来 行,每行包含三个整数

时,将 所在的集合合并。

时,输出 是否在同一集合内,是的输出
Y ;否则输出 N

输出格式

对于每一个 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

样例输出 #1

1
2
3
4
N
Y
N
Y

提示

对于 的数据,

对于 的数据,

对于 的数据,

对于 的数据,

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>
const int N=(1e5)+5;
using namespace std;
struct dsu{
vector<int> parent,size;
//Single-argument constructors must be marked explicit to avoid unintentional implicit conversions
explicit dsu(int s):parent(s+1),size(s+1,1){
iota(parent.begin(),parent.end(),0);//1,2,...,s-1
}
// 查询
int find(int x){
return parent[x]==x?x:parent[x]=find(parent[x]);
}
// 启发式合并
void unite(int x,int y){
x=find(x),y=find(y);
if(x==y)
return;
if(size[x]<size[y])
swap(x,y);
parent[y]=x;
size[x]+=size[y];
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
int n,m;
cin>>n>>m;
dsu s=dsu(n);
int x,y,z;
for(int i=0;i<m;i++){
cin>>z>>x>>y;
if(z==1){
s.unite(x,y);
}
else{
if(s.find(x)==s.find(y)){
cout<<'Y'<<endl;
}
else{
cout<<'N'<<endl;
}
}
}
return 0;
}

并查集介绍

并查集是一种数据结构,用于处理一些不交集合并及查询的问题。
并查集是森林,每个集合用一个树结构表示,树的根节点代表整个集合。

结构

1
2
3
4
5
6
7
8
struct dsu{
vector<int> parent,size;
// 单参数构造函数必须使用 explicit 关键字进行标记,以避免发生不必要的隐式类型转换。在<numeric>库里
//这里节点是从1开始到s
explicit dsu(int s):parent(s+1),size(s+1,1){
iota(parent.begin(),parent.end(),0);//1,2,...,s-1
}
};

合并

动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 简单合并
void simple_unite(int x, int y) {
parent[find(x)] = find(y);
}
// 启发式合并
void unite(int x,int y){
x=find(x),y=find(y);
if(x==y)//在同一个集合里
return;
if(size[x]<size[y])
swap(x,y);
parent[y]=x;
size[x]+=size[y];
}

查询

动画

找到了根节点,就顺便把父节点都更新成根节点

1
2
3
int find(int x){
return parent[x]==x?x:parent[x]=find(parent[x]);
}

删除

1
2
3
4
void erase(int x) {
--size[find(x)];
parent[x] = x;
}

移动

1
2
3
4
5
6
void move(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return;
parent[x] = fy;
--size[fx], ++size[fy];
}

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct dsu{
vector<int> parent,size;
explicit dsu(int s):parent(s+1),size(s+1,1){
iota(parent.begin(),parent.end(),0);//1,2,...,s-1
}
// 查询
int find(int x){
return parent[x]==x?x:parent[x]=find(parent[x]);
}
// 合并
void simple_unite(int x, int y) {
parent[find(x)] = find(y);
}
// 启发式合并
void unite(int x,int y){
//找到根结点
x=find(x),y=find(y);
//在同一个集合里
if(x==y)
return;
if(size[x]<size[y])
swap(x,y);
parent[y]=x;
size[x]+=size[y];
}
//删除
void erase(int x) {
--size[find(x)];
parent[x] = x;
}
//移动
void move(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return;
parent[x] = fy;
--size[fx], ++size[fy];
}
};


[蓝桥杯 2024 省 A] 艺术与篮球

题目描述

小蓝出生在一个艺术与运动并重的家庭中。
妈妈是位书法家,她希望小蓝能通过练习书法,继承她的艺术天赋,并练就一手好字。爸爸是一名篮球教练,他希望小蓝能通过篮球锻炼身体,培养运
动的激情和团队合作的精神。
为了既满足妈妈的期望,又不辜负爸爸的心意,小蓝决定根据日期的笔画数来安排自己的练习。首先,他会将当天的日期按照 YYYYMMDD 的格式
转换成一个 位数,然后将这 位数对应到汉字上,计算这些汉字的总笔画数。如果总笔画数超过 ,他就去练习篮球;如果总笔画数不超过 ,他就去练习书法。
例如,在 日这天,日期可表示为一个 位数字 ,其转换为汉字是“二零二四零一零一”。日期的总笔画数为 ,因此在这天,小蓝会去练习书法。
以下是汉字的笔画数对照表:

汉字 笔画数 汉字 笔画数

现在,请你帮助小蓝统计一下,在 日到
这段时间内,小蓝有多少天是在练习篮球?

输入格式

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

输出格式

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

解法

模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
const int N=(1e5)+5;
using namespace std;
int bh[]={13,1,2,3,5,4,4,2,2,2};
int bihua[32]={0};
int month[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int res=0;
int main() {
int ans=0;
for(int i=0;i<=31;i++){
bihua[i]=bh[i/10]+bh[i%10];
}
int y;
for(y=2000;y<=2023;y++){
int year_days=bihua[y/100]+bihua[y%100];
if(y%4==0)
month[2]=29;
else
month[2]=28;
for(int m=1;m<=12;m++){
int mon_days=year_days+bihua[m];
for(int d=1;d<=month[m];d++){
if(mon_days+bihua[d]>50)
ans++;
}
}
}
month[2]=29;
month[4]=13;
int year_days=bihua[y/100]+bihua[y%100];
for(int m=1;m<=4;m++){
int mon_days=year_days+bihua[m];
for(int d=1;d<=month[m];d++){
if(mon_days+bihua[d]>50)
ans++;
}
}
cout<<ans<<endl;
return 0;
}
//3228

[蓝桥杯 2024 省 A] 五子棋对弈

题目描述

“在五子棋的对弈中,友谊的小船说翻就翻?” 不!对小蓝和小桥来说,五子棋不仅是棋盘上的较量,更是心与心之间的沟通。这两位挚友秉承着 “友谊第一,比赛第二” 的宗旨,决定在一块 的棋盘上,用黑白两色的棋子来决出胜负。但他们又都不忍心让对方失落,于是决定用一场和棋(平局) 作为彼此友谊的见证。
比赛遵循以下规则:

  1. 棋盘规模:比赛在一个 的方格棋盘上进行,共有 个格子供下棋使用。
  2. 棋子类型:两种棋子,黑棋与白棋,代表双方。小蓝持白棋,小桥持黑棋。
  3. 先手规则:白棋(小蓝)具有先手优势,即在棋盘空白时率先落子(下棋)。
  4. 轮流落子:玩家们交替在棋盘上放置各自的棋子,每次仅放置一枚。
  5. 胜利条件:率先在横线、竖线或斜线上形成连续的五个同色棋子的一方获胜。
  6. 平局条件:当所有 个棋盘格都被下满棋子,而未决出胜负时,游戏以平局告终。

在这一设定下,小蓝和小桥想知道,有多少种不同的棋局情况(终局不同看成不同情况,终局相同而落子顺序不同看成同一种情况),既确保棋盘下满又保证比赛结果为平局。

输入格式

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

输出格式

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

解法

dfs,模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<bits/stdc++.h>
const int N=(1e5)+5;
using namespace std;
using ll=unsigned long long;
int arr[26];
/*
* 1 2 3 4 5
* 6 7 8 9 10
* 11 12 13 14 15
* 16 17 18 19 20
* 21 22 23 24 25
*/
ll ans=0;
void dfs(int d,int b){
if(b>13||d-b>13)
return;
if(d==26){
bool ok=false;
for(int i=7;i<=25;i+=6)
if(arr[1]!=arr[i]){
ok=true;
break;
}
if(ok)
ans++;
return;
}
for(int i=0;i<=1;i++){
arr[d]=i;
if(d%5==0){
bool ok=false;
for(int j=1;j<=4;j++){//0,1,2,3,4
if(arr[d]!=arr[d-j]){
ok=true;
break;
}
}
if(!ok)
continue;
}
if(d==21){
bool ok=false;
for(int j=9;j<=21;j+=4){//5,9,13,17,21
if(arr[5]!=arr[j]){
ok=true;
break;
}
}
if(!ok)
continue;
}
if(d>=21){
bool ok=false;
for(int j=5;j<=20;j+=5){
if(arr[d]!=arr[d-j]){
ok=true;
break;
}
}
if(!ok)
continue;
}
dfs(d+1,b+i);
}
}
int main() {
dfs(1,0);
cout<<ans<<endl;
}
//3126376


[蓝桥杯 2024 省 A] 训练士兵

题目描述

在蓝桥王国中,有 名士兵,这些士兵需要接受一系列特殊的训练,以提升他们的战斗技能。对于第 名士兵来说,进行一次训练所需的成本为 枚金币,而要想成为顶尖战士,他至少需要进行 次训练。
为了确保训练的高效性,王国推出了一种组团训练的方案。该方案包含每位士兵所需的一次训练,且总共只需支付 枚金币(组团训练方案可以多次购买,即士兵可以进行多次组团训练)。
作为训练指挥官,请你计算出最少需要花费多少金币,才能使得所有的士兵都成为顶尖战士?

输入格式

输入的第一行包含两个整数 ,用一个空格分隔,表示士兵的数量和进行一次组团训练所需的金币数。
接下来的 行,每行包含两个整数 ,用一个空格分隔,表示第 名士兵进行一次训练的金币成本和要成为顶尖战士所需的训练次数。

输出格式

输出一行包含一个整数,表示使所有士兵成为顶尖战士所需的最少金币数。

样例 #1

样例输入 #1

1
2
3
4
3 6
5 2
2 4
3 2

样例输出 #1

1
16

提示

花费金币最少的训练方式为:进行 次组团训练,花费 枚金币,此时士兵 已成为顶尖战士;再花费 枚金币,让士兵 进行两次训练,成为顶尖战士。总花费为

对于 的评测用例,

对于所有评测用例,

解法

按照需要训练数,从多往少贪心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>
#define pay second
#define num first
using namespace std;
using ll = long long;
#define int long long // 十年oi一场空,不开 long long见祖宗
typedef pair<int, int> pp;

signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr); // 正式比赛不要用这两行,用scanf和printf
int n, s;
cin >> n >> s;
vector<pair<int, int>> arr(n);
for (int i = 0; i < n; i++)
{
cin >> arr[i].pay >> arr[i].num;
}
sort(arr.begin(), arr.end());
int single_paid = 0, sum = 0, i;
for (i = n - 1; i >= 0; i--)
{
single_paid += arr[i].pay;
if (single_paid >= s) // 从第i个开始往前,集体训练更便宜,使用集体训练
{
break;
}
}
int c;
if (i == -1) // 全都是单独训练便宜
{
i = 0;
c = 0;
}
else
{
c = arr[i].num;
}
sum = c * s;
for (int j = i; j < n; j++)
{
sum += arr[j].pay * (arr[j].num - c);
}
cout << sum << endl;
return 0;
}


[蓝桥杯 2024 省 A] 团建

题目描述

小蓝正在和朋友们团建,有一个游戏项目需要两人合作,两个人分别拿到一棵大小为 的树,树上的每个结点上有一个正整数权值。
两个人需要从各自树的根结点 出发走向某个叶结点,从根到这个叶结点的路径上经过的所有结点上的权值构成了一个正整数序列,两人的序列的最长公共前缀即为他们的得分。给出两棵树,请计算两个人最多的得分是多少。

输入格式

输入的第一行包含两个正整数 ,用一个空格分隔。
第二行包含 个正整数 ,相邻整数之间使用一个空格分隔,其中 表示第一棵树结点 上的权值。
第三行包含 个正整数 ,相邻整数之间使用一个空格分隔,其中 di 表示第二棵树结点 上的权值。
接下来 行,每行包含两个正整数 表示第一棵树中包含一条
之间的边。
接下来 行,每行包含两个正整数 表示第二棵树中包含一条
之间的边。

输出格式

输出一行包含一个整数表示答案。

样例 #1

样例输入 #1

1
2
3
4
5
2 2
10 20
10 30
1 2
2 1

样例输出 #1

1
1

样例 #2

样例输入 #2

1
2
3
4
5
6
7
8
9
10
5 4
10 20 30 40 50
10 40 20 30
1 2
1 3
2 4
3 5
1 2
1 3
3 4

样例输出 #2

1
2

提示

对于 的评测用例,
对于所有评测用例,,对于任意结点,其儿子结点的权重互不相同。

解法

对两个树进行dfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <bits/stdc++.h>
#define N 200005
using namespace std;
using ll = long long;

int n[2], res = 0;

vector<vector<int>> node(2);
vector<vector<vector<int>>> g(2);
vector<vector<bool>> vis(2, vector<bool>(N));

void dfs(int u, int v, int d)
{
res = max(res, d);
map<int, int> mp; // tree2 value in tree1 value?不用map,只用双重循环过不了
for (int i = 0, l = g[0][u].size(); i < l; i++)
{
int uu = g[0][u][i];
if (vis[0][uu])
continue;
mp[node[0][uu]] = uu;
}
for (int j = 0, s = g[1][v].size(); j < s; j++)
{
int vv = g[1][v][j];
if (vis[1][vv])
continue;
if (mp.count(node[1][vv]) > 0)
{
int uu = mp[node[1][vv]];
vis[0][uu] = vis[1][vv] = true;
dfs(uu, vv, d + 1);
}
}
}
signed main()
{
for (int i = 0; i < 2; i++)
scanf("%d", &n[i]);
for (int i = 0; i < 2; i++)
{
node[i].resize(n[i] + 1);
for (int j = 1; j <= n[i]; j++)
scanf("%d", &node[i][j]);
}
int u, v;
for (int i = 0; i < 2; i++)
{
g[i].resize(n[i] + 1);
for (int j = 1; j < n[i]; j++)
{
scanf("%d%d", &u, &v);
g[i][u].push_back(v);
g[i][v].push_back(u);
}
}
vis[0][1] = vis[1][1] = true;
if (node[0][1] == node[1][1])
dfs(1, 1, 1);
printf("%d\n", res);
return 0;
}


[蓝桥杯 2024 省 A] 成绩统计

题目描述

小蓝的班上有 个人,一次考试之后小蓝想统计同学们的成绩,第 名同学的成绩为 。当小蓝统计完前 名同学的成绩后,他可以从 中选出任意 名同学的成绩,计算出这 个成绩的方差。小蓝至少要检查多少个人的成
绩,才有可能选出 名同学,他们的方差小于一个给定的值
提示: 个数 的方差 定义为:,其中 表示
的平均值,

输入格式

输入的第一行包含三个正整数 ,相邻整数之间使用一个空格分隔。

第二行包含 个正整数 ,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。如果不能满足条件,输出

样例 #1

样例输入 #1

1
2
5 3 1
3 2 5 2 3

样例输出 #1

1
4

提示

检查完前三名同学的成绩后,只能选出 ,方差为

检查完前四名同学的成绩后,可以选出 ,方差为 ,所以答案为

对于 的评测用例,保证
对于 的评测用例,保证
对于所有评测用例,保证

解法

前缀和, 二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <bits/stdc++.h>
#define N 100005
using namespace std;
using ll = long long;
ll n, k, t, kt;
ll a[N], v[N];
ll sum[N], sum2[N];
inline ll pow2(ll x)
{
return x * x;
}
bool check(int x)
{
memcpy(v + 1, a + 1, x * sizeof(ll));
sort(v + 1, v + 1 + x);
for (int i = 1; i <= x; i++)
{
sum[i] = sum[i - 1] + v[i];
sum2[i] = sum2[i - 1] + pow2(v[i]);
}
for (int i = k; i <= x; i++)
{
double ds = 1.0 * sum[i] - sum[i - k];
if ((1.0 * sum2[i] - sum2[i - k]) - (ds / k) * ds < kt)
{
return true;
}
}
return false;
}
signed main()
{
scanf("%lld%lld%lld", &n, &k, &t);
kt = k * t;
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
int l = k, r = n, res = -1;
while (l <= r)
{
int mid = l + ((r - l) >> 1);
if (check(mid))
{
res = mid;
r = mid - 1;
}
else
{
l = mid + 1;
}
}
printf("%d\n", res);
return 0;
}

[蓝桥杯 2024 省 A] 因数计数

题目描述

小蓝随手写出了含有 个正整数的数组 ,他发现可以轻松地算出有多少个有序二元组 满足 的一个因数。因此他定义一个整数对 是一个整数对 的“因数”当且仅当 分别是 的因数。他想知道有多少个有序四元组 满足 的因数,其中 互不相等。

输入格式

输入的第一行包含一个正整数
第二行包含 个正整数 ,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。

样例 #1

样例输入 #1

1
2
5
3 6 2 2 7

样例输出 #1

1
4

提示

四元组 的因子;
四元组 的因子;
四元组 的因子;
四元组 的因子。

对于 的评测用例,
对于 的评测用例,
对于所有评测用例,

解法

样例输入:
找到不同的两对因数

1
2
5
3 6 2 2 7

能找到(1,2), (3,2), (4,2), (3,4), (4,3)五对因数(对于(i,j), i≤j)
四元组,满足四个元素不重叠的两组因数,有{(1,2),(3,4)}, {(1,2),(4,3)},{(3,4),(1,2)},{(4,3),(1,2)}

先写了个直接按题意的代码void baoli(),造出测试例。
再尝试写了一个复杂度的代码,会有一半的测试例TLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*尝试的笨代码*/
#include <bits/stdc++.h>
#define N 100005
using namespace std;
using ll = long long;
ll res = 0ll;
int n; // 数据数量
int a[N]; // 数据
ll e_sum, e_same; // 边的总数量,端点对应数值相同的边的总数量(双向边总数量)
ll e[N]; // 连接边的数量
void baoli(); // 暴力验证
inline ll c2(int a) // 组合数,n*(n+1)/2,因为最后结果要*2就抵消了
{
return a * (a - 1);
}
signed main()
{
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
// baoli();
// return 0;
sort(a, a + n);
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
if (a[j] % a[i] == 0)
{

if (a[i] == a[j])
{
e[i] += 2;
e[j] += 2;
e_sum += 2;
e_same += 2;
}
else
{
e[i]++;
e[j]++;
e_sum++;
}
}
}
}
res = c2(e_sum) + e_same;
for (int i = 0; i < n; i++)
{
// cout << e[i] << ' ';
res -= c2(e[i]);
}
printf("%lld\n", res);
return 0;
}
// 以下是纯暴力验证
int baoli_ans = 0;
int t[4];
void baoli_dfs(int d)
{
if (d == 4)
{
for (int i = 0; i < 4; i++)
for (int j = i + 1; j < 4; j++)
if (t[i] == t[j])
return;
if (a[t[1]] % a[t[0]] == 0 && a[t[3]] % a[t[2]] == 0)
{
baoli_ans++;
}
return;
}
for (int i = 0; i < n; i++)
{
t[d] = i;
baoli_dfs(d + 1);
}
}
void baoli()
{
baoli_dfs(0);
cout << baoli_ans << endl;
}
/*
input:
10
1 2 2 3 3 3 4 5 6 6
//ouput: 564
*/

说明至少思路是对的,只是爆时间了👉👈。换一种方式找它们之间的因数关系。
设点输入的数量为,最大的因子为是点和多少个点有因数关系。把两个端点是因数关系的称作一条边。两条样的边称为一对边
对每一个因子,乘以倍数,获得它的倍数,直到超过为止。这样,,点x和y之间连边,它们增加边的数量均为
时间复杂度:,比好。

一个答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <bits/stdc++.h>
#define N 100005
#define M 100000
using namespace std;
using ll = unsigned long long;

ll n, m; // 数据数量,不重复的数据的数量
ll num[N]; // 不重复数据排列
ll cnt[N]; // 桶
ll e[N]; // 连接到的值不一样的边
ll res; // 结果
inline ll c2(ll a) // 组合数,n*(n+1)/2,因为最后结果要*2就抵消了
{
return a * (a - 1);
}
signed main()
{
scanf("%d", &n);
ll a;
for (register ll i = 0; i < n; i++)
{
scanf("%d", &a);
cnt[a]++;
}
for (register ll i = 1; i <= M; i++)
{
if (cnt[i] == 0)
continue;
num[m++] = i;
res += c2(cnt[i]);
for (register ll j = i * 2; j <= M; j += i)
{
e[i] += cnt[j];
e[j] += cnt[i];
res += cnt[i] * cnt[j];
}
}
res = c2(res);
for (register ll i = 0; i < m; i++)
{
res -= c2(e[num[i]] + 2 * (cnt[num[i]] - 1)) * cnt[num[i]];
res += c2(cnt[num[i]]);
}
cout << res<< endl;
return 0;
}


[蓝桥杯 2024 省 A] 零食采购

题目描述

小蓝准备去星际旅行,出发前想在本星系采购一些零食,星系内有 颗星球,由 条航路连接为连通图,第 颗星球卖第 种零食特产。小蓝想出了 个采购方案,第 个方案的起点为星球 ,终点为星球 ,对于每种采购方案,小蓝将从起点走最短的航路到终点,并且可以购买所有经过的星球上的零食(包括起点终点),请计算每种采购方案最多能买多少种不同的零食。

输入格式

输入的第一行包含两个正整数 ,用一个空格分隔。
第二行包含 个整数 ,相邻整数之间使用一个空格分隔。
接下来 行,第 行包含两个整数 ,用一个空格分隔,表示一条
航路将星球 相连。
接下来 行,第 行包含两个整数 ,用一个空格分隔,表示一个采购方案。

输出格式

输出 行,每行包含一个整数,依次表示每个采购方案的答案。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
4 2
1 2 3 1
1 2
1 3
2 4
4 3
1 4

样例输出 #1

1
2
3
2

提示

第一个方案路线为 ,可以买到第 种零食;
第二个方案路线为 ,可以买到第 种零食。

对于 20% 的评测用例,
对于所有评测用例,

解法

使用最近公共祖先LCA板子,在其中加上状态压缩DP
我博客里的lca: https://misaka-xxw.github.io/2025/02/01/lca/

首先,注意审题,题目里说n个节点,n-1条边,连通图,所以这是一个。任意选中一个根节点,那么点s到点e的最短路径,就是从s往上走到根节点,再往下走到点e。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<bits/stdc++.h>
using namespace std;
const int N=(5e5)+5;

int n,s,e;//结点数,起点,终点
vector<int> g[N];//树
int lg[N];//对数表
int c[N];//每个结点对应的食物
int depth[N],father[N][31],dp[N][31];//深度,第i结点的2^j个祖先,以及这条链路对应相应的食物购买情况
void init_lg() {
for (int i = 2; i <= n; ++i) {
lg[i] = lg[i>>1] + 1;
}
}
//利用补码的性质,找二进制里最后一个1
inline int lastof1(int x){
return x & -x;
}
//统计二进制里有多少1
inline int numof1(int x){
int a=0;
while(x){
x-= lastof1(x);
a++;
}
return a;
}
// 深度优先搜索,计算每个节点的父节点倍增表
void dfs(int me,int fa) {
for (int i = 1; i <= lg[depth[me]]; ++i){
dp[me][i]=dp[father[me][i - 1]][i - 1]|dp[me][i - 1];
father[me][i] = father[father[me][i - 1]][i - 1];
}
for (int son : g[me])
if (son!=fa){
depth[son] = depth[me] + 1;
father[son][0] = me;
dp[son][0]=c[son]|c[me];
dfs(son,me);
}
}
// 查询x和y的最低公共祖先用lca算法,改变
int lca(int x,int y){
int res=c[x]|c[y];
if(depth[x]<depth[y])
swap(x,y);
while(depth[x]>depth[y]){
int delta=lg[depth[x]-depth[y]];
res|=dp[x][delta];
x=father[x][delta];
}
if(x!=y){
for(int i=lg[depth[x]];i>=0;--i){
if(father[x][i]!=father[y][i]){
res|=(dp[x][i]|dp[y][i]);
x=father[x][i];
y=father[y][i];
}
}
res|=dp[x][0];
}
return numof1(res);
}
int main() {
int u,v,q;
scanf("%d%d",&n,&q);
init_lg();
for(int i=1;i<=n;i++){
scanf("%d",&u);
c[i]=(1<<u);
}
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,1);
while(q--){
scanf("%d%d",&s,&e);
printf("%d\n",lca(s,e));
}
return 0;
}

[蓝桥杯 2024 省 A] 封印宝石

题目描述

在一次探险中,勇者小蓝发现了 颗闪烁着奇异光芒的宝石,每颗宝石都蕴含着魔法能量,分别记作 。小蓝计划用 个特制的魔法盒子来封
印这些宝石,防止其魔法能量被滥用。
封印宝石会消耗小蓝的体力,具体地,将第 颗宝石放入第 个盒子会消耗小蓝 点体力(注:需满足 才能将第 颗宝石放入第 个盒子进行有效的封印)。小蓝也可以选择将魔法盒留空,以保存体力供后续使用。
此外,为了避免魔力相冲,每个盒子最多存放一颗宝石(每个宝石也只能放进一个盒子),且任意两个相邻盒子不能存放魔力值相同的宝石,相邻的盒子允许同时为空。
小蓝初始的体力值为 。在不超出体力限制的条件下,小蓝希望找出一种宝石的放置方法,使得宝石的魔力值在这 个盒子中的排列顺序具有最大的字典序(注:未放置宝石的盒子在此序列中记为 )。
作为勇者小蓝的追随者,请你帮他找出这一放置宝石的方法。

字典序的解释: 在本题中,字典序的大小是按照宝石的魔力值进行比较的。对于两个长度同为 的魔力值序列 ,如果存在一个位置 ,使得 对所有 成立,但是 ,则序列 在字典序上小于序列
反之,如果 ,则序列 在字典序上大于序列 。如果不存在这样的 ,则序列 和序列 的字典序相等。

输入格式

输入的第一行包含两个整数 ,用一个空格分隔,分别表示宝石的数量和小蓝的初始体力值。
第二行包含 个整数 ,相邻整数之间使用一个空格分隔,分别表示每颗宝石的魔法能量值。

输出格式

输出一行包含 个整数,相邻整数之间使用一个空格分隔,表示每个魔法盒中宝石的魔法能量值。如果某个魔法盒为空,则对应位置输出

样例 #1

样例输入 #1

1
2
3 3
1 3 2

样例输出 #1

1
3 2 -1

提示

在开始放置宝石之前,体力为 ,宝石在盒子中的排列为

  1. 将第 个宝石放进第 个盒子,得到 ,体力剩余
  2. 将第 个宝石放进第 个盒子,得到 ,体力剩余

最后宝石在盒子中的排列为 。显然,没有比这更优的放置方法。

对于 的评测用例,
对于所有评测用例,

解法

贪心+线段树
我博客里的线段树:https://misaka-xxw.github.io/2025/02/02/segment-tree/

解释

给定个数放到个位置上,放到位置上消耗个代价,合理放置这些数,让它们在代价范围内有最大的字典序。
要求:

  • 可以放在的位置区间
  • 位置可以放置区间的数
  • 可以有空的位置,相邻位置不能有相同的数。
  • 字典序的解释:两个排列方法,对于最靠前的放置了不相等的数的位置,放更大的数的那个字典序更大。没有放数就是-1。如:[5,4,3,…]比[5,4,2,…]大

贪心

  • 优先将更大的数放置到更靠前的位置
  • 对于相等的数,选更靠前
  • 两个位置不能同时放一样的数

线段树

  • 作用:区间内最大值和次大值的查询和更新
  • 从1遍历到n,查询区间内的最大值,放在位置上。
    • 如果和上一个答案相同,选次大值。
    • 使用了宝石,就要更新线段树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#include<bits/stdc++.h>

using namespace std;
const int N = (1e5) + 5;

struct node {
int num; // 数值
int pos; // 第几个数值
node() : num(-1), pos(0) {}

node(int a, int b) : num(a), pos(b) {}

bool operator>(const node &other) const {
return num != other.num ? num > other.num : pos < other.pos;
}
} node0;//空结点
typedef pair<node, node> maxs;//最大值和次大值,对应first和second
int n, m;//数量,代价,当前搜索
int a[N];//宝石数值
int loc[N];//判断叶子节点位置与线段树位置的对应关系
int ans[N];//答案,放置方法
maxs t[4 * N];//线段树,最大值和次大值
maxs res;//存放find_max时的最大值和次大值

// 更新某个节点的最大值和次大值,可能稍微快一点
void update_max(maxs &cur, const maxs l, const maxs r) {
//根据l和r,更新cur的最大值和次大值
if (l.first.num > r.first.num) {//左最大值大
cur.first = l.first;
if (l.second.num >= r.first.num)
cur.second = l.second;
else
cur.second = r.first;
} else if (l.first.num < r.first.num) {//右最大值大
cur.first = r.first;
if (l.first.num >= r.second.num)
cur.second = l.first;
else
cur.second = r.second;
} else {//左右最大值相同
cur.first = l.first;
if (l.second.num != cur.first.num && l.second.num >= r.second.num)
cur.second = l.second;
else if (r.second.num < cur.first.num)
cur.second = r.second;
else
cur.second = node0;
}
}

//效果一样的函数,似乎稍微慢一点点
void update_max2(maxs &cur, const maxs l, const maxs r) {
//根据l和r,更新cur的最大值和次大值
node tmp[] = {l.first, l.second, r.first, r.second};
sort(tmp, tmp + 4, greater<>());//从大到小排
cur.first = tmp[0];
for (int i = 1; i < 4; i++) {
if (cur.first.num != tmp[i].num) {
cur.second = tmp[i];
return;
}
}
cur.second = node0;
}

//建立线段树
void build(int l, int r, int p) {
if (l == r) {
t[p].first = node(a[l], l);
t[p].second = node0;
loc[l] = p;
return;
}
int mid = l + ((r - l) >> 1), lc = p << 1, rc = lc | 1;
build(l, mid, lc);
build(mid + 1, r, rc);
update_max(t[p], t[lc], t[rc]);
}

//查找最大值
void find_max(int l, int r, int i, int j, int p) {
if (l <= i && j <= r) {//找到了合适的区间
update_max(res, res, t[p]);
return;
}
int lc = p << 1, rc = lc | 1;
int mid = i + ((j - i) >> 1);
if (l <= mid) find_max(l, r, i, mid, lc);
if (mid < r) find_max(l, r, mid + 1, j, rc);
}
// 自底而上,修改被影响节点的最大值和次大值
void modify(int pos) {
int p = loc[pos];
t[p].first = node0;
t[p].second = node0;
for (p >>= 1; p >= 1; p >>= 1) {
update_max(t[p], t[p << 1], t[(p << 1) | 1]);
}
}

int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
memset(ans, -1, sizeof(ans));//初始化为-1,盒子为空
build(1, n, 1);//建立线段树
for (int i = 1; i <= n; i++) {
res = {node0, node0};
find_max(i, min(i + m, n), 1, n, 1);
int pos;
if (res.first.pos == 0)//最大值是一个空节点
continue;
else if (res.first.num == ans[i - 1]) {//最大值跟前一个盒子相同撞了,找次大值
if (res.second.pos == 0)//次大值是一个空节点
continue;
ans[i] = res.second.num;//选次大值
pos = res.second.pos;
} else {
ans[i] = res.first.num;//选最大值
pos = res.first.pos;
}
m -= (pos - i);
modify(pos);
}
for (int i = 1; i < n; i++) {
printf("%d ", ans[i]);
}
printf("%d\n", ans[n]);
return 0;
}

原题:https://www.luogu.com.cn/problem/P3372
教程:https://oi-wiki.org/ds/seg/

【模板】线段树 1

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 ,分别表示该数列数字的个数和操作的总个数。

第二行包含 个用空格分隔的整数,其中第 个数字表示数列第 项的初始值。

接下来 行每行包含 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 内每个数加上
  2. 2 x y:输出区间 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

样例输出 #1

1
2
3
11
8
20

提示

对于 的数据:
对于 的数据:
对于 的数据:

保证任意时刻数列中所有元素的绝对值之和

【样例解释】


线段树的结构

这个数据结构,可以高效地对区间进行更新和查询

alt text

1
2
3
int t[N];      // 线段树的值(和)
int a[N]; // 原始数组
int lazy[N]; // 懒惰标记

线段树的建立

alt text

对一整个区间和,递归,对半分成两个区间和,直到区间长度为1为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
build(1, n, 1); //开始建树

// 构造函数:初始化原始数组,构建线段树
void build(int l, int r, int p) {
if (l == r) {//区间长度为1
t[p] = a[l];
return;
}
int mid = l + ((r - l) >> 1); //中分mid=(l+r)/2
int lc = (p << 1), rc = lc | 1;//左子树lc=p*2,右子树rc=lc+1
build(l, mid, lc); //左递归建树
build(mid + 1, r, rc); //右递归建树
t[p] = t[lc] + t[rc]; //求和
}

线段树的更新

懒惰标记

没必要每次更新,都把每个点修改。而是引入一个懒惰标记。
每当访问到一个节点时,如果它有懒惰标记,就会先更新该节点和其子节点,然后清空该节点的懒惰标记。
懒惰标记表示,此区间的子区间都还欠着多少没加。

(点击标题进来看就有图片了)
操作前
(首页看排版应该是乱的)
[2,4]区间内所有数加2
(所以点击标题喵)
[3,4]区间内所有数加1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 区间加法操作,对区间[l,r]加上c
void add(int l, int r, int i, int j, int p, int c) {
if (l <= i && j <= r) { //[i,j]在更新区间[l,r]里
t[p] += (j - i + 1) * c;//该区间[i,j]更新求和
lazy[p] += c; //懒惰标记,先不往子树更新,等下一次操作再说
return;
}
int mid = i + ((j - i) >> 1); //中分mid=(l+r)/2
int lc = (p << 1), rc = lc | 1;//左子树lc=p*2,右子树rc=lc+1
if (lazy[p] && i != j) { //已经有懒惰标记,而且长度大于1
t[lc] += lazy[p] * (mid - i + 1);//左子树更新和
t[rc] += lazy[p] * (j - mid);//右子树更新和
lazy[lc] += lazy[p];//左子树更新懒惰标记
lazy[rc] += lazy[p];//右子树更新懒惰标记
lazy[p] = 0;//清除懒惰标记
}
if (l <= mid) add(l, r, i, mid, lc, c); //左子树递归更新
if (mid < r) add(l, r, mid + 1, j, rc, c);//右子树递归更新
t[p] = t[lc] + t[rc];
}

线段树的求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 区间求和操作,求[l,r]的和
int getsum(int l, int r, int i, int j, int p) {
if (l <= i && j <= r) return t[p];//[i,j]在[l,r]里,直接查询
int mid = i + ((j - i) >> 1); //中分mid=(l+r)/2
int lc = (p << 1), rc = lc | 1; //左子树lc=p*2,右子树rc=lc+1
if (lazy[p]) { //有未更新的懒惰标记
t[lc] += lazy[p] * (mid - i + 1);//左子树更新和
t[rc] += lazy[p] * (j - mid); //右子树更新和
lazy[lc] += lazy[p]; //左子树更新懒惰标记
lazy[rc] += lazy[p]; //右子树更新懒惰标记
}
lazy[p] = 0;//清除懒惰标记
int sum = 0;
if (l <= mid) sum = getsum(l, r, i, mid, lc);//左子树递归求和
if (mid < r) sum += getsum(l, r, mid + 1, j, rc);//右子树递归求和
return sum;
}

流程图

建立 更新 查询
递归出口 if 区间长度为1 if 窗口[i,j]包含于更新区间[l,r]里
t[p] = a[l] 更新当前区间和,加懒惰标志 返回当前区间和
设分界点 中分mid
左子树的索引 lc
右子树的索引 rc
分支判断 if 有懒惰 并且 长度不为1 if 只有懒惰
左、右子树更新和、懒惰标记
清除当前区间的懒惰标记
递归左右子树 无判断递归 判断区间是否越界:l <= mid < r

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <bits/stdc++.h>
using namespace std;
#define N 400005
#define int long long

int t[N]; // 线段树的值(和)
int a[N]; // 原始数组
int lazy[N]; // 懒惰标记

// 构造函数:初始化原始数组,构建线段树
void build(int l, int r, int p) {
if (l == r) {//区间长度为1
t[p] = a[l];
return;
}
int mid = l + ((r - l) >> 1); //中分mid=(l+r)/2
int lc = (p << 1), rc = lc | 1;//左子树lc=p*2,右子树rc=lc+1
build(l, mid, lc); //左递归建树
build(mid + 1, r, rc); //右递归建树
t[p] = t[lc] + t[rc]; //求和
}

void init(int n) {
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1, n, 1); // 构建线段树
}

// 区间加法操作,对区间[l,r]加上c
void add(int l, int r, int i, int j, int p, int c) {
if (l <= i && j <= r) { //[i,j]在更新区间[l,r]里
t[p] += (j - i + 1) * c;//该区间[i,j]更新求和
lazy[p] += c; //懒惰标记,先不往子树更新,等下一次操作再说
return;
}
int mid = i + ((j - i) >> 1); //中分mid=(l+r)/2
int lc = (p << 1), rc = lc | 1;//左子树lc=p*2,右子树rc=lc+1
if (lazy[p] && i != j) { //已经有懒惰标记,而且长度大于1
t[lc] += lazy[p] * (mid - i + 1);//左子树更新和
t[rc] += lazy[p] * (j - mid);//右子树更新和
lazy[lc] += lazy[p];//左子树更新懒惰标记
lazy[rc] += lazy[p];//右子树更新懒惰标记
lazy[p] = 0;//清除懒惰标记
}
if (l <= mid) add(l, r, i, mid, lc, c); //左子树递归更新
if (mid < r) add(l, r, mid + 1, j, rc, c);//右子树递归更新
t[p] = t[lc] + t[rc];
}

// 区间求和操作,求[l,r]的和
int getsum(int l, int r, int i, int j, int p) {
if (l <= i && j <= r) return t[p];//[i,j]在[l,r]里,直接查询
int mid = i + ((j - i) >> 1); //中分mid=(l+r)/2
int lc = (p << 1), rc = lc | 1; //左子树lc=p*2,右子树rc=lc+1
if (lazy[p]) { //有未更新的懒惰标记
t[lc] += lazy[p] * (mid - i + 1);//左子树更新和
t[rc] += lazy[p] * (j - mid); //右子树更新和
lazy[lc] += lazy[p]; //左子树更新懒惰标记
lazy[rc] += lazy[p]; //右子树更新懒惰标记
}
lazy[p] = 0;//清除懒惰标记
int sum = 0;
if (l <= mid) sum = getsum(l, r, i, mid, lc);//左子树递归求和
if (mid < r) sum += getsum(l, r, mid + 1, j, rc);//右子树递归求和
return sum;
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int n, m, x, y, cmd, k;
cin >> n >> m;
// 创建并初始化线段树
init(n);
// 处理操作
while (m--) {
cin >> cmd;
if (cmd == 1) {
cin >> x >> y >> k;
add(x, y, 1, n, 1, k); // 对区间 [x, y] 加上 k
} else {
cin >> x >> y;
cout << getsum(x, y, 1, n, 1) << endl; // 查询区间 [x, y] 的和
}
}
return 0;
}

【模板】线段树 2

带乘法和加法

https://www.luogu.com.cn/problem/P3373

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 4e5 + 5;
using ll = long long;
#define int long long
int n, m, q, t[M], a[N], lazy1[M], lazy2[M];//乘法、加法懒标记
void build(int l, int r, int p)
{
lazy1[p]=1;
if (l == r)
{
t[p] = a[l] % m;
return;
}
int mid = l + ((r - l) >> 1), lc = p << 1, rc = lc | 1;
build(l, mid, lc);
build(mid + 1, r, rc);
t[p] = (t[lc] + t[rc]) % m;
}
inline void update(int i,int j,int p,int mid,int lc,int rc)
{
t[lc] = (t[lc] * lazy1[p] % m + lazy2[p] * (mid - i + 1) % m) % m; // 算出来
t[rc] = (t[rc] * lazy1[p] % m + lazy2[p] * (j - mid) % m) % m;
lazy1[lc] = lazy1[lc] * lazy1[p] % m;
lazy1[rc] = lazy1[rc] * lazy1[p] % m;
lazy2[lc] = (lazy2[lc] * lazy1[p] % m + lazy2[p]) % m;
lazy2[rc] = (lazy2[rc] * lazy1[p] % m + lazy2[p]) % m;
lazy1[p] = 1;
lazy2[p] = 0;
}
void add(int l, int r, int i, int j, int p, int k)
{
if (l <= i && j <= r)
{
t[p] = (t[p] + (j - i + 1) * k % m) % m;
lazy2[p] = (lazy2[p] + k) % m;
return;
}
int mid = i + ((j - i) >> 1), lc = p << 1, rc = lc | 1;
update(i,j,p,mid,lc,rc);
if (l <= mid)
add(l, r, i, mid, lc, k);
if (mid < r)
add(l, r, mid + 1, j, rc, k);
t[p] = (t[lc] + t[rc]) % m;
}
void mul(int l, int r, int i, int j, int p, int k)
{
if (l <= i && j <= r)
{
t[p] = t[p] * k % m;
lazy1[p] = lazy1[p] * k % m;
lazy2[p] = lazy2[p] * k % m;
return;
}
int mid = i + ((j - i) >> 1), lc = p << 1, rc = lc | 1;
update(i,j,p,mid,lc,rc);
if (l <= mid)
mul(l, r, i, mid, lc, k);
if (mid < r)
mul(l, r, mid + 1, j, rc, k);
t[p] = (t[lc] + t[rc]) % m;
}
int getsum(int l, int r, int i, int j, int p)
{
if (l <= i && j <= r)
{
return t[p];
}
int mid = i + ((j - i) >> 1), lc = p << 1, rc = lc | 1;
update(i,j,p,mid,lc,rc);
int sum = 0;
if (l <= mid)
sum = getsum(l, r, i, mid, lc);
if (mid < r)
sum += getsum(l, r, mid + 1, j, rc);
return sum % m;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> q >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
build(1, n, 1);
int cmd, l, r, k;
while (q--)
{
cin >> cmd;
switch (cmd)
{
case 1:
cin >> l >> r >> k;
mul(l, r, 1, n, 1, k % m);
break;
case 2:
cin >> l >> r >> k;
add(l, r, 1, n, 1, k % m);
break;
default:
cin >> l >> r;
cout << getsum(l, r, 1, n, 1) << endl;
break;
}
}
return 0;
}

感谢贡献者🤝

Misaka-xxw
Misaka-xxw

📝
Misaka-xxw
Misaka-xxw-10032

🖋
Misaka-xxw
Misaka-xxw-20001

💻
Add your contributions

网上的教程:https://www.jianshu.com/p/0c61cec0f5d1
官方文档:https://allcontributors.org/docs/zh-cn/overview

安装应用

安装该Github应用的网址:https://allcontributors.org/docs/zh-cn/bot/installation

all-contributors-cli和使用配置

依赖npm或者yarn

1
2
npm i -D all-contributors-cli
npx all-contributors init

回答它问你的问题,会获得类似这样的一个json。失败的话可能是因为你的仓库没公开什么的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"projectName": "XMU-CS-exam",
"projectOwner": "Misaka-xxw",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": true,
"commitConvention": "gitmoji",
"contributorsPerLine": 7,
"linkToUsage": true
}


还会在README.md文档里生成一些html标签,不要删除。会在之间自动生成贡献者表格
1
2
3
4
5
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

常用指令:

1
2
3
npx all-contributors check # 检查未添加的贡献者
npx all-contributors add Misaka-xxw content # 添加贡献者Misaka-xxw,职责是内容
npx all-contributors generate # 生成表格

然后直接push即可o( ̄▽ ̄)ブ

附录:官方文档的职责表

Emoji/Type Represents Comments
🔊 audio Audio Podcasts, background music or sound effects
♿️ a11y Accessibility Reporting or working on accessibility issues
🐛 bug Bug reports Links to issues reported by the user on this project
📝 blog Blogposts Links to the blogpost
💼 business Business Development People who execute on the business end
💻 code Code Links to commits by the user on this project
🖋 content Content e.g. website copy, blog posts are separate
🔣 data Data Links to contributed data for the project (both tests and datasets)
📖 doc Documentation Links to commits by the user on this project, Wiki, or other source of documentation
🎨 design Design Links to the logo/iconography/visual design/etc.
💡 example Examples Links to the examples
📋 eventOrganizing Event Organizers Links to event page
💵 financial Financial Support People or orgs who provide financial support, links to relevant page
🔍 fundingFinding Funding/Grant Finders People who help find financial support
🤔 ideas Ideas & Planning
🚇 infra Infrastructure Hosting, Build-Tools, etc. Links to source file (like travis.yml) in repo, if applicable
🚧 maintenance Maintenance People who help in maintaining the repo, links to commits by the user on this project
🧑‍🏫 mentoring Mentoring People who mentor new contributors, links to the repo home
📦 platform Packaging Porting to support a new platform
🔌 plugin Plugin/utility libraries Links to the repo home
📆 projectManagement Project Management
📣 promotion Promotion
💬 question Answering Questions Answering Questions in Issues, Stack Overflow, Gitter, Slack, etc.
🔬 research Research Literature review.
👀 review Reviewed Pull Requests
🛡️ security Security Identify and/or reduce security threats, GDPR, Privacy, etc
🔧 tool Tools Links to the repo home
🌍 translation Translation Links to the translated content
⚠️ test Tests Links to commits by the user on this project
✅ tutorial Tutorials Links to the tutorial
📢 talk Talks Links to the slides/recording/repo/etc
📓 userTesting User Testing Links to user test notes
📹 video Videos Links to the video

原题:https://www.luogu.com.cn/problem/P3379

【模板】最近公共祖先(LCA)

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式

第一行包含三个正整数 ,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 行每行包含两个正整数 ,表示 结点和 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 行每行包含两个正整数 ,表示询问 结点和 结点的最近公共祖先。

输出格式

输出包含 行,每行包含一个正整数,依次为每一个询问的结果。

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
9
10
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

样例输出 #1

1
2
3
4
5
4
4
1
4
4

提示

对于 的数据,

对于 的数据,

对于 的数据,不保证

样例说明:

该树结构如下:

第一次询问: 的最近公共祖先,故为

第二次询问: 的最近公共祖先,故为

第三次询问: 的最近公共祖先,故为

第四次询问: 的最近公共祖先,故为

第五次询问: 的最近公共祖先,故为

故输出依次为


倍增法是一种用于在树中快速找到两个节点的最近公共祖先(LCA)的算法。它的核心思想是通过预处理和二进制跳跃来高效地找到公共祖先。

看了里面的题解,大概分为三步

深度优先搜索,获得每个结点的深度和祖先

(点击标题进来看就有图片了)
dfs找深度和祖先

1
2
3
4
5
6
7
8
9
10
11
12
void dfs(int me,int fa) {
// 填充倍增表,父节点表的大小是log2(depth)+1
for (int i = 1; i <= lg[depth[me]]; ++i)
father[me][i] = father[father[me][i - 1]][i - 1];
for (int son : g[me])
if (son!=fa)//不回环
{
depth[son] = depth[me] + 1;
father[son][0] = me;
dfs(son,me);
}
}
  • father[x][i] 表示节点 x 向上跳 2^i 步后的祖先节点。
  • lg[depth[x]] 表示节点 x 的深度的对数值,用于确定最大跳跃步数。对数表可以提前生成好
1
2
3
4
void init_lg() {
for (int i = 2; i < N; ++i)
lg[i] = lg[i>>1] + 1;
}

表格如下:

x 0 1 2 3 4 5 6 7 8 9
lg[x] 0 0 1 1 2 2 2 2 3 3

寻找公共祖先,先将更深的点回溯到与另一个点深度相同的位置


快速回溯到深度相同
1
2
3
4
5
if (depth[x] < depth[y])
swap(x, y);//让x比y深
while(depth[x]>depth[y])// 让x和y在同一深度
x=father[x][lg[depth[x]-depth[y]]];
if (x == y) return x;

逐跳往上找公共祖先


逐跳往上找公共祖先

1
2
3
4
for (int i = lg[depth[x]]; i >= 0; --i) // 逐步跳到二分的父节点
if (father[x][i] != father[y][i])
x = father[x][i],y = father[y][i];
return father[x][0];
  • 从最大的 i 开始(即从最大的跳跃步数开始),逐步减少 i,检查节点 xy2^i 祖先是否相同。
  • 如果 father[x][i]father[y][i] 不相同,则将 xy 更新为它们的 2^i 祖先。
  • 目的:将 xy 同时向上移动,直到它们的祖先相同。
  • 当循环结束时,xy 的最近公共祖先就是 father[x][0],即 xy 的直接父节点。

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;

int n, q, root;
vector<int> g[N];

int lg[N]; // 用于存储每个节点的最大深度所需的倍增表大小
int father[N][31]; // father[i][j]:结点i的2^j祖先
int depth[N];//depth[i]:结点i的深度

// 初始化lg数组,计算log2的值
void init_lg() {
for (int i = 2; i < N; ++i)
lg[i] = lg[i>>1] + 1;
}

// 深度优先搜索,计算每个节点的父节点倍增表
void dfs(int me,int fa) {
// 填充倍增表,父节点表的大小是log2(depth)+1
for (int i = 1; i <= lg[depth[me]]; ++i)
father[me][i] = father[father[me][i - 1]][i - 1];
for (int son : g[me])
if (son!=fa)//不回环
{
depth[son] = depth[me] + 1;
father[son][0] = me;
dfs(son,me);
}
}

// 查询x和y的最低公共祖先
int lca(int x, int y) {
if (depth[x] < depth[y])
swap(x, y);//让x比y深
while(depth[x]>depth[y])// 让x和y在同一深度
x=father[x][lg[depth[x]-depth[y]]];
if (x == y) return x;
for (int i = lg[depth[x]]; i >= 0; --i) // 逐步跳到二分的父节点
if (father[x][i] != father[y][i])
x = father[x][i],y = father[y][i];
return father[x][0];
}

int main() {
scanf("%d%d%d", &n, &q, &root);
init_lg(); // 初始化lg数组
int x, y;
for (int i = 1; i < n; i++) {
scanf("%d%d", &x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
// 初始化根节点
dfs(root,root); // 执行DFS,填充倍增表
while (q--) {
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y)); // 查询LCA
}
return 0;
}
0%