当前位置: 首页 > news >正文

第十六届蓝桥杯大赛软件赛C/C++赛道题解

本篇文章会持续更新,后面将十六届C/C++的各组的省赛、国赛全部写完。有错误欢迎大家指出。大家可以先刷完蓝桥杯真题卷再来看哦~

蓝桥杯真题卷 - 蓝桥云课

目录

省赛

C组

数位倍数

2025

2025图形

最短距离

冷热数据队列

倒水

拼好数

登山

B组

移动距离

客流量上限

可分解的正整数

产值调整

画展布置

水质检测

生产车间

装修报价

A组

寻找质数

黑白棋

抽奖

红黑树

黑客

好串的数目

地雷阵

扫地机器人

研究生组

数位倍数 

IPv6

变换数组

最大数字

冷热数据队列

01串

甘蔗

原料采购

京津冀

密密摆放

脉冲强度之和

双子星的讯息

25之和

旗帜

消消乐

树上寻宝

基因配对

栈与乘积

数列差分

翻转硬币

破解信息

交互

国赛【未完待续... ...】


省赛

C组

数位倍数

【解法】模拟

遍历每一个数,计算各个数位之和是否是5的倍数即可。这里可以用不断模10得到最低位,然后除以10将最低位删除即可

【代码参考】

#include <iostream>
using namespace std;bool check(int x)
{int sum = 0;while (x){sum += x % 10;x /= 10;}return sum % 5 == 0;
}
int main()
{int ret = 0;for (int i = 1; i <= 202504; i++)if (check(i))ret++;cout << ret << endl;return 0;
}

2025

【解法】模拟

遍历每一个数,计算每一个数的位数上0,2,5的个数是否满足条件

【代码参考】

#include <iostream>
#include <iostream>
#include <cstring>
using namespace std;
int cnt[10]; 
bool check(int x)
{memset(cnt, 0, sizeof cnt);while (x){cnt[x % 10]++;x /= 10;}if (cnt[0] < 1 || cnt[2] < 2 || cnt[5] < 1) return false;return true;
}
int main()
{int c = 0;for (int i = 1; i <= 20250412; i++)if (check(i))c++;cout << c << endl;return 0;
}

2025图形

【解法】模拟

题目给的范围是1≤h,w≤100,范围给的不大直接循环打印就可以了。对于第 i 行第 j 列,对应的就是2025中 (i+j)mod 4的值

【代码参考】

#include <iostream>
using namespace std;
int a[4] = {2,0,2,5};
int main()
{int h,w;cin>>h>>w;for(int i = 0;i < h;i++){for(int j = 0;j < w;j++){cout << a[(i + j) % 4];}cout << endl;}return 0;
}

最短距离

【解法】贪心

贪心策略:将显示器和插座的坐标排序,然后按照从小到大依次对应

下面是用反证法证明:

情况一:∣bi − ai∣ + ∣bj − aj∣ = ∣bj − ai∣ + ∣bi − aj∣

情况二:∣bi − ai∣ + ∣bj − aj∣ < ∣bj − ai∣ + ∣bi − aj∣

等等情况,如果列出所有情况,最后发现选择从小到大一一对应更优

【代码参考】

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 5e4 + 10;
int n;
LL a[N], b[N];int main()
{cin >> n;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= n; i++) cin >> b[i];sort(a + 1, a + 1 + n);sort(b + 1, b + 1 + n);LL sum = 0;for (int i = 1; i <= n; i++) sum += abs(a[i] - b[i]);cout << sum << endl;return 0;
}

冷热数据队列

【解法】链表、哈希表

查找用哈希表,头插、尾插、删除利用链表。这个题的数据范围比较小,所以用双端队列应该也可以通过

【代码参考】

#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;
int n1, n2, m;
list<int> q1, q2;
unordered_map<int, list<int>::iterator> mp1, mp2;
int main()
{cin >> n1 >> n2 >> m;while (m--){int x; cin >> x;if (mp1.count(x)){// 删除q1.erase(mp1[x]);mp1.erase(x);// 移动q1.push_front(x);mp1[x] = q1.begin();}else if (mp2.count(x)){// 删除q2.erase(mp2[x]);mp2.erase(x);// 移动q1.push_front(x);mp1[x] = q1.begin();}else{q2.push_front(x);mp2[x] = q2.begin();}if (q1.size() > n1){int x = *q1.rbegin();q1.pop_back();mp1.erase(x);q2.push_front(x);mp2[x] = q2.begin();}if (q2.size() > n2){int x = *q2.rbegin();q2.pop_back();mp2.erase(x);}}for (auto x : q1) cout << x << " ";cout << endl;for (auto x : q2) cout << x << " ";cout << endl;return 0;
}

倒水

【解法】二分

这道题的二分还是比较明显的,假设最终结果为ret,那么倒水时杯中水量大于ret时一定无法完成,反之如果小于等于ret时一定可以完成。我们再用check函数去模拟倒水过程中,杯子水为x时,是否能完成

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL n, k;
LL a[N];
bool check(LL x)
{for (int i = 1; i <= k; i++){LL t = 0; // 前⾯的杯⼦向 i 号杯⼦倒了多少⽔for (int j = i; j <= n; j += k){if (a[j] + t < x) return false;t = a[j] + t - x;}}return true;
}
int main()
{cin >> n >> k;for (int i = 1; i <= n; i++) cin >> a[i];int l = 0, r = 1e5;while (l < r){int mid = (l + r + 1) >> 1;if (check(mid)) l = mid;else r = mid - 1;}cout << l << endl;return 0;
}

拼好数

【解法】贪心+分类讨论

我们不关心给的数是什么,我们只关心出现6的次数。预设一个cnt[i]:出现6的次数为i时,一共有多少数。以样例2为例,第一个数出现6的次数为6,那么cnt[6]+1,第二个数出现6的次数为3,那么cnt[3]+1,同理剩下的为cnt[3]+1、cnt[3]+1、cnt[1]+1、cnt[1]+1、cnt[1]+1。此时cnt[6]为1,cnt[3]为3,cnt[1]为3,表示有1个数含有6个6,3个数含有3个6,3个数含有1个6

这里有一个贪心的思想:

1、如果一个数6出现的次数大于等于6,结果+1即可,因为等于6就是好数了,多了也一样;

2、如果能用1去拼好数尽量用1。因为1使用的范围很局限,所以剩下更有用是其他数去拼有更多的可能,所以如果出现了4+1+1和4+2,那么先选择4+1+1;

3、能用少的数就不用多的数,5+1比5+1+1更好;

4、优先考虑更容易拼成6的数,即拼凑顺序为5,4,3,2

所以我们就可以知道拼数的优先级
    * 5:5+1 > 5+2 > 5+3 > 5+4 > 5+5
    * 4:4+1+1 > 4+2 > 4+3 > 4+4
    * 3:3+1+2 > 3+3 > 3+2+2
    * 2:2+2+2

这里说一下为什么3+3 > 3+2+2。因为3、3、3、3、2、2。如果选择3+2+2优先级大的话就会有拼不了的,但实际可以(3+3)(3+3)(2+2+2)这样拼

【代码参考】

#include <iostream>
using namespace std;
int n;
int cnt[10], ret;
// 计算⼀下 6 出现的次数
int calc(int x)
{int c = 0;while (x){if (x % 10 == 6) c++;x /= 10;}return c;
}
int main()
{cin >> n;for (int i = 1; i <= n; i++){int x; cin >> x;int t = calc(x);if (t >= 6) ret++;else cnt[t]++;}// 分类讨论/*优先级* 5:5+1 > 5+2 > 5+3 > 5+4 > 5+5* 4:4+1+1 > 4+2 > 4+3 > 4+4* 3:3+1+2 > 3+3 > 3+2+2* 2:2+2+2*/if (cnt[5]){// 5 + 1、5 + 2、5 + 3、5 + 4for (int i = 1; i <= 4 && cnt[5]; i++){// 注意执⾏的顺序if (cnt[i] <= cnt[5]) ret += cnt[i], cnt[5] -= cnt[i], cnt[i] = 0;else ret += cnt[5], cnt[i] -= cnt[5], cnt[5] = 0;}// 5 + 5ret += cnt[5] / 2;}if (cnt[4]){// 4 + 1 + 1if (2 * cnt[4] <= cnt[1]) ret += cnt[4], cnt[1] -= 2 * cnt[4], cnt[4] =0;else ret += cnt[1] / 2, cnt[4] -= cnt[1] / 2, cnt[1] %= 2;// 4 + 2、4 + 3for (int i = 2; i <= 3; i++){if (cnt[i] <= cnt[4]) ret += cnt[i], cnt[4] -= cnt[i], cnt[i] = 0;else ret += cnt[4], cnt[i] -= cnt[4], cnt[4] = 0;}ret += cnt[4] / 2;}if (cnt[3]){// 3 + 1 + 2if (cnt[1] && cnt[2]){int t = min(min(cnt[1], cnt[2]), cnt[3]);ret += t;cnt[1] -= t; cnt[2] -= t; cnt[3] -= t;}// 3 + 3ret += cnt[3] / 2;cnt[3] %= 2;// 3 + 2 + 2cnt[2] += cnt[3];}ret += cnt[2] / 3;cout << ret << endl;return 0;
}

登山

【解法】逆序对+图论

分析题中给的4个行走条件:在1的条件下,我们从\left (p,q \right )走到\left ( i,q \right ),此时的位置符合2的条件,我们又可以从\left ( i,q \right )走到\left (p,q \right )。3、4的条件同理。于是我们得到下面两条性质:

        1、如果可以从\left ( a,b\right )走到\left ( c,d \right ),那么也可以从\left ( c,d \right )走到\left ( a,b \right ),相当于逆序对

        2、对于某一行/列而言,从前往后/从上往下看,前一个数大于后一个数就可以行走,相当于                就是逆序对之间有一条边。

所以所有的行走方式会构成一个图结构。那么我们可以运用二维转一维的方式将网格抽象成图结构。此时我们行走的话会有一个个的连通块,这些连通块我们可以用并查集维护,求每一个连通块的最大权值:

        1、当联通块中的最大值作为集合的代表元素;

        2、每次合并的时候,让较大元素作为父结点即可

接下来我们要考虑的问题就是如何找到所有的边?

暴力来找的话,m行,n列在极端条件下,行需要m*n^{2} 条边,列需要n*m^{2}条边,一共就有m*n^{2} +n*m^{2}条边。无论是时间还是空间都太大了

贪心来想的话,以5,4,3,2,1,9,7,6为例。从前往后走,4、3其实不需要连,因为5比4大,那么最后肯定是5和3相连。那么同理,3、2、1都和5相连,7和6都应该和9相连。这时候我们发现连接完后的序列是递增的。我们可以用栈来解决,从前往后遍历:

        1、如果栈为空或者栈顶元素比当前元素小,说明前面没有比它大的元素,直接进栈;

        2、如果栈顶元素比当前元素大,说明可以建边,那就把栈中比当前元素大的元素全部出栈。

注意,此时不应该是当前元素进栈,而是让这个联通块中最大的元素进栈。因为我们要的是左边所有比它大的元素,如果仅仅让当前元素进栈,会漏掉很多边。

【代码参考】

#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, m;
int a[N];
int fa[N]; // 并查集
int st[N], top; // 栈
// ⼆维转⼀维
int get_id(int i, int j)
{return i * m + j;
}
int find(int x)
{return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{x = find(x), y = find(y);if (x == y) return;// ⼩的合并到⼤的if (a[x] >= a[y]) fa[y] = x;else fa[x] = y;
}
int main()
{cin >> n >> m;for (int i = 1; i < n * m; i++) fa[i] = i;for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)cin >> a[get_id(i, j)];// 建图for (int i = 0; i < n; i++){top = 0;for (int j = 0; j < m; j++){int t = top, id = get_id(i, j);while (top && a[st[top]] > a[id]){merge(st[top], id);top--;}if (t == top) st[++top] = id;else st[++top] = st[t];}}for (int j = 0; j < m; j++){top = 0;for (int i = 0; i < n; i++){int t = top, id = get_id(i, j);while (top && a[st[top]] > a[id]){merge(st[top], id);top--;}if (t == top) st[++top] = id;else st[++top] = st[t];}}double ret = 0;for (int i = 0; i < n * m; i++) ret += a[find(i)];printf("%.6lf\n", ret / (n * m));return 0;
}

B组

移动距离

【解法】数学

如果我们按照「右、弧、右、弧、右、弧......」这样的形式,过程和代码会相当麻烦;这是第一题,我们假设:只向右走一次,然后走一个弧,到达坐标(233, 666)。

我们来证明一下假设是否正确,假如多走了一个弧,也就是「右、弧、右、弧」的形式,那么行走方式如下

由于我们是以\left ( 0,0 \right )为圆心画弧,因此「右、弧、右、弧」的最后⼀个弧的起点 E 一定在弧AB上。只要证明OC+CD+DE > OA+AE即可。

由弧长公式得:CD = OC × ∠COD ,AE = OA × ∠AOE ;由三角形不等式得:OD + DE > OE ,即OC + DE > OE ;代入不等式,需证:OC × ∠COD > OA × ∠AOE 。

在△DOE 中,由正弦定理得:\frac{OD}{\sin \angle DEO}=\frac{OE}{\sin \angle ODE},即\frac{OC}{\sin \angle DEO}=\frac{OA}{\sin \angle ODE}。代入不等式得:\frac{\sin \angle DEO}{\sin \angle ODE}>\frac{\angle AOE }{\angle COD},等量代换得:\frac{\sin \angle AOE}{\sin \angle COD}>\frac{\angle AOE }{\angle COD}

需证:\frac{\sin x}{x}>\frac{\sin y}{y},当x<y且在[0,\frac{\pi }{2}]成立。

对函数\frac{\sin x}{x}求导并对分子继续求导得:\cos x-x\sin x-x\cos x=-x\sin x[0,\frac{\pi }{2}]小于0。

所以x\cos x-\sin x单调递减,最大值为0,所以\frac{\sin x}{x}单调递减,当x<y时,\frac{\sin x}{x}>\frac{\sin y}{y}

因此,多一次「右、弧」操作就会使得路径增加,原猜想就是正确的。

【代码参考】

#include <iostream>
#include <cmath>
using namespace std;
int main()
{double x = 233, y = 666;printf("%.0lf", sqrt(x * x + y * y) * (1 + atan(y / x)));return 0;
}

客流量上限

【解法】数学

当 i = j 时,a_{i}^{2}\leq i^{2}+2025,即a_{i}^{2}\leq \sqrt{i^{2}+2025}

1、对于\displaystyle f(x) = \sqrt{x_{i}^{2}+2025}而言,是⼀个单调递增的函数,并且当x 在增大的过程中,2025 的影响就会变小,最终会趋近于 \displaystyle f(x)=x

2、那么将 i 从 1 枚举到 2025 ,打表会发现:当1013 ≤ i ≤ 2025 时,a_{i}\leq i

3、 因为a_{i} \sim a_{1013} 都小于等于 1013 ,所以a_{i} \sim a_{1013}会把 1~1013 全部占完,所以[1014,2025] 之间,a_{i}=i

当 i != j 时,设 i < j :

1、 已知 i = 1013 时,a_{i}\leq i ,以及1014 ≤ i ≤ 2025 时,a_{i}= i

2、 当 1 ≤ i ≤ 1012,  1014 ≤ j ≤ 2025 时,满足a_{i}*a_{j}=a_{i}*j\leq i*j+2025 ,两边同时除以j ,可得:a_{i}\leq i+\frac{2025}{j},即a_{i}\leq i+1

3、当 1 ≤ i ≤ 1012,  j = 1013 时,也会满足 a_{i}*a_{j}=a_{i}*j\leq i*j+2025,即a_{i}\leq i+1

4、当 1 ≤ i, j ≤ 1012 时,此条件也成立:a_{i}*a_{j}\leq (i + 1)(j + 1) = i * j + i + j + 1;其中 i + j + 1 ≤ 1012 + 1012 + 1 = 2025 。

综上所述:

  • a_{1} 能取 1, 2 ;
  • a2 能取 1, 2, 3 ;
  • ...
  • a1013 能取 1, 2, 3...1013 
  • a1014 只能取 1014 
  • ....
  • a2025 只能取 2025

从前往后考虑,a_{1}有 2 种选法,a_{2} 在 a_{1} 选过之后,还有 2 种选法;a_{3} 在前两个选完之后,还 有2 种选法;......;a_{1013} 在前面都选完之后,只剩下 1 种选法。因此,总共的方案数为2^{1012} 。

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
int main()
{LL ret = 1;for (int i = 1; i <= 1012; i++){ret = ret * 2 % mod;}cout << ret << endl;return 0;
}

可分解的正整数

【解法】思维题

除了1以外的任何数x ,都可以构造成: [−(x − 1), −(x − 2), ..., −1, 0, 1, ..., x − 1, x] 使其总和为x 。因此,统计序列中非1元素的个数即可。

【代码参考】

#include <iostream>
using namespace std;
int main()
{int n; cin >> n;int cnt = 0;while (n--){int x; cin >> x;if (x != 1) cnt++;}cout << cnt << endl;return 0;
}

产值调整

【解法】找规律

在一直均分的过程中,三个数会逐渐变得相等。等三个数相等之后,就可以跳出循环,直接输出。两个数之差的最大值为 10^{9},每次均分⼀次之后,差值至少变成之前的一半;那么最多需要 \log 10^{9} 次,就能变得相等;因此,整体的时间复杂度为 T*\log n

【代码参考】

#include <iostream>
using namespace std;
int main()
{int T; cin >> T;while (T--){int a, b, c, k; cin >> a >> b >> c >> k;while (k--){int x = (b + c) / 2, y = (a + c) / 2, z = (a + b) / 2;a = x; b = y; c = z;if (a == b && a == c) break;}cout << a << " " << b << " " << c << endl;}return 0;
}

画展布置

【解法】数学+排序

观察式子可以发现,当选取的 m 个数之间,相邻元素的差值尽可能的小,总和才会更小。那么,可以将所有的数排序,每次选取相邻 m 个数,找出计算结果最小的一组即可。由于所有元素升序排列,区间内的计算结果就等于两端元素的平方差。

【代码参考】

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL n, m, a[N];
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++) cin >> a[i];sort(a + 1, a + 1 + n);LL ret = 1e10;for (int i = 1; i + m - 1 <= n; i++){ret = min(ret, a[i + m - 1] * a[i + m - 1] - a[i] * a[i]);}cout << ret << endl;return 0;
}

水质检测

【解法】01bfs

从最左边的某一个 # 出发,遇到 . 需要加一个检测器,遇到 # 无需任何操作。那么,相当于就是从左侧的 # 出发,走向走右侧的 # ,路径的边权要么为 0 ,要么为 1 ,可以用 01bfs 解决。

当然其他题解有使用贪心和动态规划去做的,这里不讲解。

【代码参考】

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1e6 + 10;
int n;
char g[2][N];
int dist[2][N];
bool st[2][N];
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };
int bfs(int i, int j)
{int ret = 0;memset(dist, 0x3f, sizeof dist);deque<pair<int, int>> q;q.push_back({ i, j });dist[i][j] = 0;while (q.size()){auto t = q.front(); q.pop_front();int a = t.first, b = t.second;if (st[a][b]) continue;st[a][b] = true;if (g[a][b] == '#') ret = max(ret, dist[a][b]);for (int k = 0; k < 4; k++){int x = a + dx[k], y = b + dy[k];if (x < 0 || x >= 2 || y < 0 || y >= n || st[x][y]) continue;int w = g[x][y] == '#' ? 0 : 1;dist[x][y] = min(dist[a][b] + w, dist[x][y]);if (w) q.push_back({ x, y });else q.push_front({ x, y });}}return ret;
}
int main()
{cin >> g[0] >> g[1];n = strlen(g[0]);for (int j = 0; j < n; j++){if (g[0][j] == '#'){cout << bfs(0, j) << endl;return 0;}if (g[1][j] == '#'){cout << bfs(1, j) << endl;return 0;}}// 处理边界情况cout << 0 << endl;return 0;
}

生产车间

【解法】树上背包+bitset优化

由题意得,叶子结点和非叶结点需要不同处理:

  • 对于叶子结点而言,只能向上提供 0 或者 w_{i} 的材料;
  • 对于非叶结点而言,需要根据子树能够提供的权值,整合一下之后,才能确定自己能向上提供多少材料。

1、状态表示:f[x][i] 表示:编号为 x 的结点,能否向上提供 i 单位的材料。

2、状态转移方程: 当回溯到 x 结点时,孩子结点 y 可以看作是一个一个小组,每个小组能提供的材料都在 f[y] 里面存储。这就是⼀个分组背包问题。

对于每一个孩子结点,遍历它们能提供的材料信息 j ,那么 f[x][i] = f[y][j]&&f[x][i − j] 但是要注意,每⼀个结点都有上限,需要把超过上限部分置为 false。

3、初始化:叶子结点单独处理,只能向上提供 0 或者 w_{i} ,就将叶子结点的 f[x][0] 以及 f[x][w_{i}] 标记为 true。

注意我们的状态值要么为 0,要么为 1。所以我们可以用 bitset 优化。

bitset 就是一个比特位的集合,我们可以将其看成一个 01 串,每⼀位占⼀个bit。

用 bitset 创建⼀个变量之后,可以进行单点0/1修改,左移右移以及按位运算操作。这些操作方式与我们对整数的操作方式一致。相当于我们拥有一个比⼀个整数更长的 01 串集合。

【代码参考】

未优化的版本

#include <iostream>
#include <vector>
using namespace std;
const int N = 1010;
int n, w[N];
vector<int> edges[N];
int f[N][N];
void dfs(int x, int fa)
{f[x][0] = true;if (edges[x].size() == 1 && x != 1) // 叶⼦结点{f[x][w[x]] = true;return;}for (auto y : edges[x]){if (y == fa)continue;dfs(y, x);for (int j = w[x]; j >= 0; j--)for (int k = min(w[y], j); k >= 0; k--)f[x][j] = f[x][j] || (f[y][k] && f[x][j - k]);}
}
int main()
{cin >> n;for (int i = 1; i <= n; i++)cin >> w[i];for (int i = 1; i < n; i++){int a, b;cin >> a >> b;edges[a].push_back(b);edges[b].push_back(a);}dfs(1, 0);for (int i = w[1]; i >= 0; i--)if (f[1][i]){cout << i << endl;break;}return 0;
}

优化后的版本

#include <iostream>
#include <vector>
#include <bitset>
using namespace std;
const int N = 1010;
int n;
int w[N];
vector<int> edges[N];
bitset<N> f[N];
void dfs(int x, int fa)
{f[x][0] = 1;// 叶⼦结点if (edges[x].size() == 1 && x != 1){f[x][w[x]] = 1;return;}for (auto y : edges[x]){if (y == fa) continue;dfs(y, x);bitset<N> tmp = f[x];for (int i = 1000; i >= 0; i--)if (f[y][i])f[x] |= (tmp << i);}for (int i = 1000; i > w[x]; i--)f[x][i] = 0;
}
int main()
{cin >> n;for (int i = 1; i <= n; i++) cin >> w[i];for (int i = 1; i < n; i++){int a, b; cin >> a >> b;edges[a].push_back(b);edges[b].push_back(a);}dfs(1, 0);for (int i = w[1]; i >= 0; i--)if (f[1][i]){cout << i << endl;break;}return 0;
}

装修报价

【解法】数学

把所有可能的组合罗列出来之后会发现,形如 +(...) 和 −(...) 是会成对出现的,求和之后会抵消。 因此,只有前缀全部都是异或运算,才会对结果产生贡献。

设只有异或运算的前缀为a1 ∼ ak,异或和为 sum 。此时下⼀个运算只能是 +/− ,接下来的运算 就是三者中任取其一。因此,这段异或和对总结果的贡献为 sum*2*3^{n-k-1}。a1 ∼ an 这个区间只会对结果产生 1 的贡献,特殊处理⼀下。

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, mod = 1e9 + 7;
LL n, a[N];
LL mi[N]; // 3 的 i 次幂
int main()
{cin >> n;for (int i = 1; i <= n; i++) cin >> a[i];mi[0] = 1;for (int i = 1; i <= n; i++) mi[i] = mi[i - 1] * 3 % mod;LL ret = 0, s = 0;for (int i = 1; i < n; i++){s ^= a[i];ret = (ret + s * 2 % mod * mi[n - i - 1] % mod) % mod;}s ^= a[n];ret = (ret + s) % mod;cout << ret << endl;return 0;
}

A组

寻找质数

【解法】枚举

找出第2025个质数即可

【代码参考】

#include <iostream>
using namespace std;
bool check(int x)
{if (x < 2) return false;for (int i = 2; i <= x / i; i++){if (x % i == 0) return false;}return true;
}
int main()
{int cnt = 0;for (int i = 1; i <= 1e5; i++){if (check(i)){cnt++;if (cnt == 2025){cout << i << endl;break;}}}return 0;
}

黑白棋

【解法】找规律

按照规律,往里边填数即可

1、(3, 4) 和 (1, 3) 一定是黑色,否则会让周围不合法

2、(3, 1) 和 (6, 4) 必须是黑色,不然填上白色之后,剩下全填黑色就会不合法

3、(6, 3)、(6, 6) 和 (2, 1) 必须是白色,不然会让中间两个黑,以及上下两个不合法

4、接下来把剩下能直接填的格子填上,(6, 1) 黑, (4, 1) 、 (3, 1) 白

5、(2, 2) 和 (5, 2) 不能是白色,不然剩下三个格子要填黑色,不合法

6、局势很明朗了,剩下的随便填填就满了

【代码参考】

#include <iostream>
using namespace std;
int main()
{cout << "101001010011101100010110011001100110" << endl;return 0;
}

抽奖

【解法】模拟

按照题目要求写出来代码即可

【代码参考】

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, x, y, z;
int a[N], b[N], c[N];
int t[N];
int calc()
{t[0] = a[x], t[1] = b[y], t[2] = c[z];if (t[0] == t[1] && t[0] == t[2]) return 200;if (t[0] + 1 == t[1] && t[1] + 1 == t[2]) return 200;sort(t, t + 3);if (t[0] == t[1] || t[1] == t[2]) return 100;if (t[0] + 1 == t[1] && t[1] + 1 == t[2]) return 100;return 0;
}
int main()
{cin >> n;for (int i = 0; i < n; i++) cin >> a[i];for (int i = 0; i < n; i++) cin >> b[i];for (int i = 0; i < n; i++) cin >> c[i];int m; cin >> m;int sum = 0;while (m--){int dx, dy, dz; cin >> dx >> dy >> dz;x = (x + dx) % n;y = (y + dy) % n;z = (z + dz) % n;sum += calc();}cout << sum << endl;return 0;
}

红黑树

【解法】递归

将所有结点编号:

可以得到如下关系:

  • 当前结点编号为 x ,父结点编号为 \frac{x+1}{2} ;
  • 根据父结点的颜色,可以得到左右孩子的颜色。

那么,根据当前结点的编号,可以一路向上找到根结点。然后根据路径,确定当前结点的颜色。 因此,可以设计一个 dfs 函数,一路向上找到根节点,然后回溯的时候确定当前点的颜色:

  • 函数头为 dfs(n, k) ,表示第 n 层,第 k 号结点的颜色;
  • 返回值为 1 表示红色,返回值为 0 表示黑色;
  • 根据dfs(n − 1, (x+1)/2)的返回值,来确定当前点的颜色。

【代码参考】

#include <iostream>
using namespace std;
bool dfs(int n, int k)
{// 根节点if (n == 1) return true;int c = dfs(n - 1, (k + 1) / 2);if (k & 1) return c; // 左孩⼦else return !c; // 右孩⼦
}
int main()
{int T; cin >> T;while (T--){int n, k; cin >> n >> k;if (dfs(n, k)) cout << "RED\n";else cout << "BLACK\n";}return 0;
}

黑客

【解法】数论

1. 找出所有的 n 和 m :

输入的数减 2 等于  n × m ,设这个数为 N 。试除法求 N 的约数,就可以找出所有的 n × m 。那么,需要在输入的数中找出,是否有 n 和 m 和两个数。可以在读入数据的时候,用数组 cnt[i] 记录每⼀个数出现的次数,可以快速找到 n 和 m ,以及它们出现的次数;

2、找出一组 n 和 m 之后,如何求方案数:

将这组  (n, m) 走之后,问题就变成:剩下的数放在矩阵中,一共有多少种不同的方案。将矩阵展 开成一行,其实就是剩下的数的排列问题,即\frac{N!}{\prod cnt[i]!} 。由于结果需要取模,可以先预处理出1 ∼ N 的阶乘逆元。

3、优化:

     a. 没有必要每次找到一组 (n, m) 之后,就重新求一次删掉一个 n 和 m 之后的 \frac{N!}{\prod cnt[i]!}

     b. 可以先求出所有数的 S=\frac{N!}{\prod cnt[i]!} 。找到一组 (n, m) 之后,可以先用 S 乘上 cnt[n]!\times cnt[m]! ,然后再除以 (cnt[n] - 1)!\times (cnt[m]-1)!

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10, mod = 1e9 + 7;
LL n, f[N], g[N];
LL cnt[N];
LL qpow(LL a, LL b, LL p)
{LL ret = 1;while (b){if (b & 1) ret = ret * a % p;a = a * a % p;b >>= 1;}return ret;
}
// 初始化 阶乘表 和 阶乘逆元表
void init()
{int n = 5e5;f[0] = 1;for (int i = 1; i <= n; i++) f[i] = f[i - 1] * i % mod;g[n] = qpow(f[n], mod - 2, mod);for (int i = n - 1; i >= 0; i--) g[i] = g[i + 1] * (i + 1) % mod;
}
int main()
{init();cin >> n;for (int i = 1; i <= n; i++){int x; cin >> x;cnt[x]++;}n -= 2;LL s = f[n];for (int i = 1; i < N; i++) s = s * g[cnt[i]] % mod;LL ret = 0;for (int i = 1; i <= n / i; i++){if (n % i) continue;int j = n / i; // i * j = n// 判断是否能找出来 i 和 jif (!cnt[i] || !cnt[j]) continue;if (i == j && cnt[i] < 2) continue;if (i != j){LL t = s * f[cnt[i]] % mod * f[cnt[j]] % mod * g[cnt[i] - 1] % mod* g[cnt[j] - 1] % mod;ret = (ret + t + t) % mod;}else{LL t = s * f[cnt[i]] % mod * g[cnt[i] - 2] % mod;ret = (ret + t) % mod;}}cout << ret << endl;return 0;
}

好串的数目

【解法】模拟+数学

根据好串的定义,只能从以下两种情况产生好串:

1、 一个长度为 x 的连续非递减序列,任意拿出一段都是一个好串,⼀共有 x × (x + 1) 个。

2.、如果左右挨着两个长度分别为 x 和 y 的连续非递减序列,左边截⼀段,右边截⼀段,两者一共可组成 x × y 个好串。

因此,从前往后枚举所有的连续非递减序列,同时记录前一个连续非递减序列的长度,就可以计算出所有好串的数量。

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
string s;
int n;
int main()
{cin >> s; n = s.size();LL ret = 0, prev = 0;for (int i = 0; i < n; ){int j = i + 1;while (j < n && (s[j] == s[j - 1] || s[j] == s[j - 1] + 1)) j++;LL cur = j - i;// ⼀左⼀右对好串的贡献ret += prev * cur;// 当前对好串的贡献ret += (cur + 1) * cur / 2;prev = cur;i = j;}cout << ret << endl;return 0;
}

地雷阵

【解法】几何+区间合并

1.、找出所有能被攻击的区域: 根据一个地雷,可以在[0,\frac{\pi }{2}]上确定哪个⻆度区间会被攻击:

由图可得,被攻击的区域为:[\theta -\beta ,\theta +\beta ]。其中 \theta =\arctan \frac{y}{x} ,\beta =\arcsin \frac{r}{x^{2}+y^{2}}。因此,根据每⼀个地雷,都可以计算出这样⼀个被攻击的区间。把所有的区间合并在⼀起,就是能够 被攻击的区域。

2、 合并区间,过程比较固定,记 sum 为合并后的区间长度:

        a、先将所有区间按照左端点从小到大排序,此时所有重叠区域的区间就在一起;

        b、以第⼀个区间 [L, R] 作为标准,从前往后遍历剩下区间 [l, r] :

              ▪ 如果当前区间 l ≤ R ,说明有重叠,合并:R = max(R, r) ;

              ▪ 如果 l > R ,说明没有重叠,之前的 [L, R] 就是⼀个合并后的区间。统计长度 sum+ = R − L ,然后更新 L = l, R = r ,继续遍历后面的区间。

3、最终结果:1-\frac{2\times sum}{\pi }

【代码参考】

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
struct node
{double x, y; // 每⼀个圆所覆盖的区间
}a[N];
bool cmp(node& a, node& b)
{return a.x < b.x;
}
int main()
{cin >> n;for (int i = 1; i <= n; i++){double x, y, r; // ⼀定要⽤ double 存cin >> x >> y >> r;double t1 = atan(y / x), t2 = asin(r / sqrt(x * x + y * y));a[i] = { t1 - t2, t1 + t2 };}// 区间合并sort(a + 1, a + 1 + n, cmp);double l = a[1].x, r = a[1].y, sum = 0;for (int i = 2; i <= n; i++){double x = a[i].x, y = a[i].y;if (x <= r) r = max(r, y); // 有重叠else{sum += r - l;l = x; r = y;}}sum += r - l;printf("%.3lf\n", 1.0 - sum / asin(1));return 0;
}

扫地机器人

【解法】基环树+拓扑排序+树上dp+单调队列

先讲解一下基环树

树结构是 n 个顶点,n − 1 条边,如果多一条边会变成一个图结构。此时这个图结构中只有一个环, 这样的图称为基环树。 基环树,就是基于环的⼀棵树。因此,这个结构可以看做是:环 + 环上的树构成。

基环树虽然比较复杂,但是相关问题基本上都和树有关。解法也是比较固定的,一般都是分类讨论+ 具体情况具体分析。

这题难度不小,先给出解法:基环树 + 拓扑排序找环 + 树形 dp 求树的直径 + 单调队列

在本题中,求的是一条最长的路径。在树中,对应的问题就是树的直径。那么在基环树中,根据最终结果的可能性,可以分为以下几种情况:

1、最终结果在环上的某个树上,如下图紫色部分:

2、最终结果 = 一棵子树的最长链 + 当前子树的次长链 + 整个环:

3、最终结果 = 一棵子树的最长链 + 另一棵子树的最长链 + 环上一部分:

只要求出每种情况的最大值,就是最终结果。

1、求每种情况之前,需要先知道环的位置,即环中具体的点。方法多样,这里介绍拓扑排序找环。 注意,这道题的树是无向树,因此我们会存储两个方向的边,其实并不是常见的拓扑图。但是,叶子结点的入度为 1 。删掉叶子结点之后,对应结点如果非环,入度也会变成 1 。只有环上的点,入度永远大于 1 。因此,仅需修改一下拓扑排序的判断条件,就能找到环: 

• 可以将所有入度为 1 的点加入队列; 

• 每次出队之后修改相连点的入度信息,如果入度变为 1 ,说明是叶子结点,加入队列。

循环结束,剩下入度大于 1 的点,就是环上的点。

2、在情况三中,我们需要知道环上两点之间的其余点。因此,还需要求出环上点的次序。 从一个环上任意一个点做一次 dfs ,直到回到该点。那么 dfs 的顺序就是环的顺序。

3、处理情况一:

本质就是在树上找出最长链,也就是树的直径,解法有两种。这里推荐树形dp,因为后续情况中需要以根节点出发的最长链,树形 dp 维护的就是这个信息。

但是要注意,这道题其实不是常规的树的直径,因为这道题的权值在点上,而不在边上。那么仅需稍微修改一下状态转移方程即可。

• 状态表示:f[x] 表示从 x 出发,向叶子结点方向走的最长链的长度。

• 状态转移方程:遍历所有孩子节点 y ,注意不要走到环上

   ◦ 先更新当前能找到的最长 + 次长:tmp = max(tmp, f[x] + f[y]) ;

   ◦ 再更新状态:f[x] = max(f[x], f[y]) 

所有孩子遍历结束之后,加上当前结点的权值即可:ret = max(ret,tmp + t[x]),f[x]+ = t[x]

4、处理情况二:

情况二相较于情况一,无非就是先找到以 x 为根的的子树中,经过 x 的最长链,然后再加上环上结 点的权值之和。但是要减掉 x 的权值,因为计算了两次。

其中,以 x 为根的的子树中,经过 x 的最长链再减掉 t[x] ,正好就是树形 dp 在递归到 x 结点时 的 tmp 变量。

所以,树形 dp 的时候,设置一个返回值,将 tmp 返回。然后在调用的时候,拿到返回值,加上环上 的权值之和 sum 就是情况二。

5、处理情况三:

此时我们已经有了 f[x] ,即从环上每一个点出发的最长链,但是还要找出环上的一段。需要⼀个处理环的技巧:将环从任意一个位置拆成一个序列,然后复制一遍。此时,对于复制之后的序列,任意一个长度为环长的序列都能对应到原始的环上。

对于环上两点之间的权值之和,我们可以先预处理一个前缀和数组 sum ,方便我们快速计算区间的权值。

如果两层 for 循环枚举两个位置 i, j(j < i) ,肯定吃不消,先观察一个式子( len 为环长):

                                f[i] + f[j] + sum[i] − sum[j − 1],(i − j + 1 ≤ len)

其中 f[i] + sum[i] 是定值,如果能让 f[j] − sum[j − 1] 最大,那么就可以找出以 i 位置为右端点时候的最大权值。那么,可以用单调队列来维护区间 [i − len + 1,i − 1] 中 f[i] − sum[i − 1] 的最 ⼤值即可。可以将时间优化到 O(n) 。

至此,三种情况全部分析结束。

【代码参考】

#include <iostream>
#include <queue>
#include <vector>
#include <deque>
using namespace std;
const int N = 1e6 + 10;
int n;
int t[N];
vector<int> edges[N];
int in[N];
int a[N], cnt; // 环上的点
int sum[N]; // 环上的前缀和
bool st[N]; // 标记环上的点
int f[N], ret; // f[i] 表⽰:从 i 开始的最⻓链
// 按顺序标记环上的点
void dfs1(int x)
{a[++cnt] = x;st[x] = true;for (auto y : edges[x])if (in[y] > 1 && !st[y])dfs1(y);
}
void topsort()
{queue<int> q;// 叶⼦结点⼊队for (int i = 1; i <= n; i++)if (in[i] == 1)q.push(i);while (q.size()){int x = q.front(); q.pop();for (auto y : edges[x]){in[y]--;if (in[y] == 1) q.push(y);}}for (int i = 1; i <= n; i++)if (in[i] > 1){dfs1(i);break;}
}
// 返回以 x 为中转点的最⻓路径,但不包括 x 点
int dfs(int x, int fa)
{int tmp = 0;for (auto y : edges[x]){// 注意不能⾛到环上if (y == fa || in[y] > 1) continue;dfs(y, x);tmp = max(tmp, f[y] + f[x]);f[x] = max(f[x], f[y]);}f[x] += t[x];ret = max(ret, tmp + t[x]); // 情况⼀return tmp;
}
int main()
{cin >> n;for (int i = 1; i <= n; i++) cin >> t[i];for (int i = 1; i <= n; i++){int a, b; cin >> a >> b;edges[a].push_back(b);edges[b].push_back(a);in[a]++; in[b]++;}topsort();// 复制⼀遍,计算前缀和for (int i = 1; i <= cnt; i++) a[i + cnt] = a[i];for (int i = i; i <= cnt * 2; i++) sum[i] = sum[i - 1] + t[a[i]];for (int i = 1; i <= cnt; i++){int x = dfs(a[i], 0); // 情况⼀ret = max(ret, x + sum[cnt]); // 情况⼆}// 情况三deque<int> q;for (int i = 1; i <= cnt; i++) f[a[i]] -= t[a[i]];for (int i = 1; i <= cnt * 2; i++){int x = a[i];if (q.size()) ret = max(ret, f[x] + sum[i] + f[a[q.front()]] -sum[q.front() - 1]);while (q.size() && f[a[q.back()]] - sum[q.back() - 1] <= f[x] - sum[i - 1]) q.pop_back();q.push_back(i);while (q.size() && q.back() - q.front() + 1 >= cnt) q.pop_front();}cout << ret << endl;return 0;
}

研究生组

数位倍数 

同C组第一题,这里就不再写了。


IPv6

【解法】枚举+数学

由题意得,IPv6 一共分为 8 段,可以一段一段考虑长度 len[i] 和出现次数 cnt[i] 。如果不考虑删除的情况下,所有可能的长度总和为\left ( \sum_{i=1}^{8} len[i]+7\right )\times \prod_{i=1}^{8}cnt[i] 。

因此,可以暴力枚举每一段的长度 0 ∼ 4 ,然后计算出每一种情况对结果的贡献即可。其中当前段的长度为 0 表示此时填写的就是 0 ,0 其实也有一个长度,最终统计总长度时再加上 0 的个数 zero 即可。  

但是,实际长度需要删除一些 '0' 和 ':' 。为了保证压缩后的长度最短,我们需要删除长度应该是:删除 '0' 和 ':' 的个数的最大值。因此,根据我们每一段的决策,需要找出最长的删除长度。设连续的 '0' 出现了 sum 次:

• 如果压缩的 '0' 在内部:那么 '.' 需要删除 sum − 1 次,一共需要删除 sum + sum − 1 ;

• 如果压缩的 '0' 在两端:那么 '.' 需要删除 sum − 2 次,⼀共需要删除 sum + sum − 2 ;

• 边界情况:如果选了 8 段 0 ,实际需要删除的长度为 sum + sum − 3 。

那么,在确定好每一段的长度之后,从前往后维护连续 '0' 的个数,就可以找出需要删除的最长长度 del。最终结果为\left ( \sum_{i=1}^{8} len[i]+7-del+zero\right )\times \prod_{i=1}^{8}cnt[i]

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 10, mod = 1e9 + 7;
// 每⼀段的⻓度, 每⼀段出现的次数
LL l[N], c[N];
LL kind[N]; // kind[i] 表⽰:⻓度为 i 时,⼀共出现的次数
LL ret;
void dfs(int id)
{if (id > 8){// 计算结果LL len = 7, cnt = 1;LL del = 0, num = 0;for (int i = 1; i <= 8; i++){len += (l[i] == 0 ? 1 : l[i]);cnt = cnt * c[i] % mod;if (l[i] == 0){if (i > 1 && l[i - 1] == 0) num++;else num = 1;if (i - num + 1 == 1 || i == 8) del = max(del, num + num - 2);// 在两端else del = max(del, num + num - 1); // 在中间}}if (num == 8) del = 13;len = len - del;ret = (ret + len * cnt % mod) % mod;return;}for (int i = 0; i <= 4; i++){l[id] = i;c[id] = kind[i];dfs(id + 1);}
}
int main()
{kind[0] = 1, kind[1] = 15;for (int i = 2; i <= 4; i++) kind[i] = kind[i - 1] * 16;dfs(1);cout << ret << endl;return 0;
}

变换数组

【解法】模拟

根据题意模拟即可。lowbit 操作: x & -x ,可以将二进制的最低位的 1 删掉。统计一个数的二进制中 1 的个数:不断执行 lowbit 操作,看能执行几次。

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;
int n, m;
LL a[N];
LL calc(LL x)
{LL cnt = 0;while (x){x -= x & -x;cnt++;}return cnt;
}
int main()
{cin >> n;for (int i = 1; i <= n; i++) cin >> a[i];cin >> m;while (m--){for (int i = 1; i <= n; i++) a[i] *= calc(a[i]);}for (int i = 1; i <= n; i++) cout << a[i] << " ";return 0;
}

最大数字

【解法】贪心+高精度

贪心:可以通过重定义排序规则,确定每⼀个数的优先级。 设两个数对应的二进制字符串表示分别为 s 和 t ,排序规则为: s + t > t + s。

确定好优先级之后,剩下的就是将所有数对应的二进制字符拼在一起,还原出对应的十进制表示的数即可。但是,由于数据范围很大,要用高精度来计算。 二进制转十进制: 1、设最终结果为 x ;2、不断执行 x × 2 ,然后 x + 0/1 即可。

【代码参考】

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3.5e5 + 10;
int n;
string a[N];
int ret[N], id; // 最终结果
bool cmp(string& a, string& b)
{return a + b > b + a;
}
int main()
{cin >> n;// ⼗进制转⼆进制for (int i = 1; i <= n; i++){int t = i;while (t){a[i] += t % 2 + '0';t /= 2;}reverse(a[i].begin(), a[i].end());}// 排序sort(a + 1, a + 1 + n, cmp);// ⼆进制转⼗进制for (int i = 1; i <= n; i++){for (auto ch : a[i]){// 乘 2int c = 0; // 进位for (int i = 0; i <= id; i++){ret[i] = ret[i] * 2 + c;c = ret[i] / 10;ret[i] %= 10;}if (c) ret[++id] = c; // 处理进位// 加上当前位的权值c = ch - '0';for (int i = 0; i <= id && c; i++){ret[i] = ret[i] + c;c = ret[i] / 10;ret[i] %= 10;}if (c) ret[++id] = c; // 处理进位}}for (int i = id; i >= 0; i--) cout << ret[i];return 0;
}

冷热数据队列

同C组第五题,这里就不再写了。


01串

【解法】找规律+位运算

如果第一次遇到这种类型的题目,基本上很难总结出规律。因此,就把这道题的各种规律对应的结论和实现方式记住,往后再遇到类似题目的时候就可以直接使用。

本题用到的规律:

  • 规律一:1 ~ n 中二进制表示的长度为 len ,一共有 2^{len-1} 个数,因此这批数的总长度为 len\times 2^{len-1} 。
  • 规律二:0\sim 2^{k}-1 的二进制表示中,1 的个数为 k\times 2^{k-1} 。

第一个规律比较好证明,随便列举几个数就可以看出来。

重点看规律二,以 0\sim 2^{5}-1 的二进制表示为例:

可以发现,每一行中,1 出现的次数和 0 出现的次数一样,因此总的个数为 \frac{5\times 2^{5}}{2}=5\times 2^{5-1}

解决本题:

这个 01 串的前 x 位可以分成两部分:一批数的完整二进制 + ⼀个数的不完整二进制(有可能不存 在)。例如 0110111 就是完整的  0, 1, 2 和不完整的 3 。计算结果时,可以先计算完整部分 1 的个 数,再计算不完整的部分 1 的个数。

因此,第一步就是将 x 拆分成完整 1 ~ n + 不完整,第二步统计完整部分,第三步计算不完整部 分。

1、先将 01 串分割成完整部分 + 不完整部分。为了方便,先将第一位的 0 去掉,x− = 1 ,不影响 最后结果。

根据规律一,可以将 x 按照长度 len 从小到大拆分,并且计算出完整的数的上限 n 。当拆到不能再拆时,x / len 就是最后一段完整数的个数,x mod len 就是不完整的部分。

2、计算 0 ~ n 的二进制表示中 1 的个数。按照 n 的二进制表示中 1 的位置,将 0 ~ n 分类,然后分别统计。设 n 的二进制表示为 11010100 ,可以根据 1 的位置,将 0 ~ n 分成以下几个区间:

      a. 第 2 位是 1 ,分成:11010000 ~ 11010011 ;

      b. 第 4 位是 1 ,分成:11000000 ~ 11001111 ;

      c. 第 6 位是 1 ,分成:10000000 ~ 10111111 ;

      d. 第 7 位是 1 ,分成:00000000 ~ 01111111 。

从下往上看,仅仅缺少一个 n ,但是此时每个区间的贡献都能根据规律二快速得出。因此,根据 n 的二进制表示,就可以快速算出 0 ~ n - 1 之间 1 出现的个数。如果先让 n + = 1 ,此时算出来的就是包含原始 n 的 1 的个数。

3、计算不完整的数 1 的个数。此时 x 表示不完整数的前 x 位,那么 n + 1 右移 len - x 之后的数中 1 的个数就是不完整部分的 1 。

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
// 统计⼆进制表⽰中,⼀共有多少个 1
int bit_count(LL x)
{int c = 0;while (x){c++;x -= x & -x;}return c;
}
// 0 ~ n - 1 中⼀共有多少个 1
LL get_sum(LL n)
{LL k = 0; // 标记 1 的位数LL ret = 0; // 统计结果while (n){if (n & 1){// 统计结果ret += k * (1ll << (k - 1)) + bit_count(n >> 1) * (1ll << k);}n >>= 1;k++;}return ret;
}
int main()
{LL x; cin >> x;// 拆分成完整部分和不完整部分x--;LL len = 1, n = 0;while (len * (1ll << (len - 1)) <= x){x -= len * (1ll << (len - 1));n += (1ll << (len - 1));len++;}n += x / len;x %= len;// 完整部分:1 ~ n// 不完整的部分:n + 1 这个数的前 x 位,n + 1 的⼆进制表⽰⻓度为 len// 计算结果n++;cout << get_sum(n) + bit_count(n >> (len - x)) << endl;return 0;
}

甘蔗

【解法】动态规划

1、状态表示: f[i][j] 表示:将第 i 根甘蔗砍成长度为 j 时,前 i 根甘蔗高度差符合要求时,最少需要砍的次数。 那么 f[n][1] ∼ f[n][a[n]] 的最小值就是结果。

2、状态转移方程:

需要砍的次数分两类:

a. 对于当前甘蔗,如果高度 j < a[i] ,需要砍一次;如果高度  j = a[i]  ,不需要砍,设次数为 x 

b. 对于前面面的甘蔗,根据与前一根甘蔗的高度差 b[k] ,分两种情况讨论:

  • 前⼀根甘蔗被砍后的高度为 j − b[k] ,此时最少需要砍的次数为:f[i − 1][j − b[k]] ;
  • 前一根甘蔗被砍后的高度为 j + b[k] ,此时最少需要砍的次数为:f[i − 1][j + b[k]] 。

因此,状态转移方程为 f[i][j] = x + min(f[i − 1][j − b[k]], f[i − 1][j + b[k]]) 。填表的时候,注意越界访问的情况。

3、初始化: 由于求的是最小值,可以先将整张表初始化为正无穷,然后把第一行初始化。 对于第一根甘蔗:

a. 高度不变,不需要砍:f[1][a[1]] = 0 ;

b. 高度降低 j < a[i] ,需要砍:f[1][j] = 0 。

【代码参考】

#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 1010;
int n, m;
int a[N], b[N];
int f[N][M];
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= m; i++) cin >> b[i];// 初始化memset(f, 0x3f, sizeof f);for (int j = 0; j < a[1]; j++) f[1][j] = 1;f[1][a[1]] = 0;for (int i = 2; i <= n; i++)for (int j = 0; j <= a[i]; j++)for (int k = 1; k <= m; k++){int w = j == a[i] ? 0 : 1;if (j + b[k] <= a[i - 1]) f[i][j] = min(f[i][j], f[i - 1][j +b[k]] + w);if (j - b[k] >= 0) f[i][j] = min(f[i][j], f[i - 1][j - b[k]] +w);}int ret = 0x3f3f3f3f;for (int j = 0; j <= a[n]; j++) ret = min(ret, f[n][j]);if (ret == 0x3f3f3f3f) cout << -1 << endl;else cout << ret << endl;return 0;
}

原料采购

【解法】贪心

从前往后行走,当走到距离 c_{i} 的位置时,选择目前为止单价最小的即可。用大根堆就可以模拟出整个过程。

创建一个大根堆,存储<货物单价,货物库存>,以货物的单价为关键字排序。同时维护两个变量,分别为堆中货物的总库存 cnt ,堆中货物的总价值 sum 。从前往后遍历每⼀个采购点:

1、先将当前点的货物信息扔进堆中,同时维护 sum 和 cnt ;

2、如果堆里面的总库存 cnt − 堆顶元素的库存 ≥ m,那就把堆顶扔掉,因为永远也选不到;

3、当堆合法之后,此时堆中的元素就是能够选择的单价最小的元素集合。由于堆顶可能多出来一部分,用 sum 减去堆顶元素多出来的部分,加上路程开销,就是此时的最少花费。

【代码参考】

#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PLL;
LL n, m, o, ret = 2e18;
priority_queue<PLL> heap; // ⼤根堆
LL cnt, sum; // 堆中原料的总数量以及总价值
int main()
{cin >> n >> m >> o;for (int i = 1; i <= n; i++){LL a, b, c; cin >> a >> b >> c;heap.push({ a, b });cnt += b;sum += a * b;while (cnt - heap.top().second >= m){cnt -= heap.top().second;sum -= heap.top().second * heap.top().first;heap.pop();}if (cnt >= m){ret = min(ret, sum - (cnt - m) * heap.top().first + o * c);}}if (ret == 2e18) cout << -1 << endl;else cout << ret << endl;return 0;
}

京津冀

第十六届由于极端天气原因,京津冀地区原本的考试时间推迟,所以他们用的这一套试卷。这里由于各组的非常多的题重复,所以就这里就把各组的题放在一起了。

密密摆放

【解法】找规律

200, 240, 250 正好可以被 40, 30, 50 整除,因此小箱子可以将大箱子填满。个数为:200 × 240 × 250 ÷ 40 ÷ 30 ÷ 50 。

【代码参考】

#include <iostream>
using namespace std;
int main()
{cout << (200 / 40) * (250 / 50) * (240 / 30) << endl;return 0;
}

脉冲强度之和

【解法】枚举

由第一个条件得:p=\frac{(k+k+9)*10}{2}=10k+45。因此,可以从小到大枚举 k ,一直枚举到 p>20255202 。然后判断计算出来的 p 是否每一位都相同即可。

【代码参考】

#include <iostream>
using namespace std;
bool check(int x)
{int tmp = x % 10;while (x){if (tmp != x % 10) return false;x /= 10;}return true;
}
int main()
{int ret = 0;for (int k = 1; ; k++){int p = 10 * k + 45;if (p > 20255202) break;if (check(p)) ret += p;}cout << ret << endl;return 0;
}

双子星的讯息

【解法】数学

a^{2}=n+20255202,b^{2}=n+10244201 ,两式相减得:(a + b) × (a − b) = 10011001 。那么,找出一组 a 和 b 对应的就是一个 n 。

1、 用试除法找出 10011001 所有的约数 d ;

2、 枚举 a + b 的和,即10011001 的某个约数 d_{i} ,再枚举其中一个数 a(1\leq a\leq d_{i}),计算另一个数 b=d_{i}-a ,判断 a, b 是否合法即可:

        a. (a + b) × (a − b) = 10011001 ;

        b. 保证 n 是正整数:a × a > 20255202 ;

        c. 保证 n 是正整数:b × b > 10244201 。

【代码参考】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL d[N], n;
int main()
{LL x = 10011001;for (int i = 1; i <= x / i; i++){if (x % i == 0){d[++n] = i;if (i != x / i) d[++n] = x / i;}}int cnt = 0;for (int i = 1; i <= n; i++){for (LL a = 0; a <= d[i]; a++){LL b = d[i] - a;if (b < 0 || a < b) continue;if ((d[i] * (a - b) == x) && a * a > 20255202 && b * b > 10244201)cnt++;}}cout << cnt << endl;return 0;
}

25之和

【解法】循环

循环就行了,这题就像是刚刚学习编程时写的题,很简单

【代码参考】

#include <iostream>
using namespace std;
int main()
{int n, sum = 0; cin >> n;for (int i = 0; i < 25; i++)sum += n + i;cout << sum << endl;return 0;
}

旗帜

【解法】模拟

数据范围不大直接循环填数就可以了。对于第 i 行,第 j 列,对应的就是LANQIAO中第(i+j)mod 7是否是 1 或 5 即可。

【代码参考】

#include <iostream>
using namespace std;
int n, m;
int main()
{cin >> n >> m;int cnt = 0;for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)if ((i + j) % 7 == 1 || (i + j) % 7 == 5)cnt++;cout << cnt << endl;return 0;
}

消消乐

【解法】贪心+双指针

题目中要使得消除之后的长度最长。根据消除规则,无法消除之后的状态为 B...BA...A 。 这里可以贪心,为了使的长度最长,尽量保留左边的 B 和右边的 A 。因此,我们应该尽量用前面的 A 去消除右边的 B 。可以定义左右双指针来模拟这个过程。

【代码参考】

#include <iostream>
using namespace std;
int main()
{string s; cin >> s;int n = s.size();int l = 0, r = n - 1;while (l < r){while (l < r && s[l] == 'B') l++;while (l < r && s[r] == 'A') r--;if (l < r){n -= 2;l++; r--;}}cout << n << endl;return 0;
}

树上寻宝

【解法】树的遍历

根据题意,小蓝可以走到距离根节点 2 × k 的所有结点,因此遍历树一遍即可。

【代码参考】

#include <iostream>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int n, k;
LL w[N], sum;
vector<int> edges[N];
bool st[N];
void dfs(int x, int dep)
{sum += w[x];st[x] = true;if (dep == k) return;for (auto y : edges[x]){if (st[y]) continue;dfs(y, dep + 1);}
}
int main()
{cin >> n >> k; k *= 2;for (int i = 1; i <= n; i++) cin >> w[i];for (int i = 1; i < n; i++){int a, b; cin >> a >> b;edges[a].push_back(b);edges[b].push_back(a);}dfs(1, 0);cout << sum << endl;return 0;
}

基因配对

【解法】动态规划

先想一想暴力的解法:

1、两层 for 循环两个端点 i、j;

2、然后从 i,j 出发,找出以 i,j  为起点的最⻓匹配的长度 len 。那么 len 就是以 i,j 为起点时满足条件的基因对的数量

但是这样需要三层循环,时间复杂度为O(n^{3}),会超时

考虑用动态规划预处理出以 i, j 为起点时的最长匹配长度,优化掉一维。但是,我们一般喜欢以某个位置为结尾。因此:

1、状态表示: f[i][j] 表示:两个字符串分别以 i, j 位置为结尾时,最长的匹配长度。

2、状态转移方程:

根据最后一个位置的字符,分情况讨论:

    a. 如果 s[i] = s[j] ,说明不会匹配, f[i][j] = 0 ;

    b. 如果 s[i] != s[j],说明可以匹配,找出以 i − 1, j − 1 结尾时的最长即可,即 f[i − 1][j − 1] + 1;

但是要注意,用 f 数组时,有可能最长匹配长度会大于 j - i ,与题目要求的字符串不符,注意特殊 处理。

【代码参考】

#include <iostream> 
using namespace std;
const int N = 1010;
int n;
int f[N][N];
int main()
{string s; cin >> s;n = s.size();s = ' ' + s;// 预处理for (int i = 1; i <= n; i++)for (int j = i + 1; j <= n; j++)if (s[i] != s[j])f[i][j] = f[i - 1][j - 1] + 1;// 统计结果int ret = 0;for (int i = 1; i <= n; i++)for (int j = i + 1; j <= n; j++)ret += min(j - i, f[i][j]); // 不能出现区间重叠的情况cout << ret << endl;return 0;
}

栈与乘积

【解法】线段树+栈

线段树解法:

由于总的操作次数在1e5 ,因此可以创建⼀个线段树维护[1,10^{5}]区间内的乘积。 此时:

1、向栈顶加入元素:线段树的单点修改;

2. 弹出栈顶元素:无需修改,直接让有效元素减⼀即可;

3. 查询最顶端 y 个数的乘积:线段树的区间查询。

因此,可以完美解决这个问题。但是,需要注意:

1、乘积会超出 int 的最大值。当乘积超出⼀定范围时,区间维护的乘积信息直接置为一个无穷大即可,不然会溢出;

 2、特殊数字特殊处理:0 和任何数相乘之后变成 0 ;1 乘以任何数都等于 1。

但是我们发现如果暴力枚举只有操作三会超时。这里可以优化一下

栈的解法:

每一个数 x ,都是大于等于 0 ,小于2的30次方 ,且为整数。因此,在相乘的过程中,有如下性质:

a. 当 x > 1 时,最多乘 32 次,就会大于等于2的32次方 。

b. 当 x = 1 时,乘积不会改变。因此,这个数无需存在栈中,操作三就可以省很多时间;

c. 当 x = 0 时,乘积为 0 。因此,可以单独存储。如果查询区间有 0,可以快速得到查询结果。

所以,我们可以维护两个栈:存非零元素的栈,以及存零元素的栈。对于操作三,最多只会执行32 次。

【代码参考】

线段树解法:

#include <iostream>
using namespace std;
typedef unsigned long long LL;
#define lc p << 1
#define rc p << 1 | 1
const int N = 1e5 + 10;
const LL M = (1ull << 32);
struct node
{int l, r;LL mul;
} tr[N << 2];
int top;
void pushup(int p)
{LL a = tr[lc].mul, b = tr[rc].mul;// 分情况讨论:if (a == 0 || a == 1 || b == 0 || b == 1) // 如果是特殊数字 0/1tr[p].mul = a * b;                             else if (a == M || b == M) tr[p].mul = M; // 如果有个区间已经⽆穷⼤else if (a * b >= M) tr[p].mul = M;  // 相乘之后已经⽆穷⼤else tr[p].mul = a * b;
}
void build(int p, int l, int r)
{tr[p] = {l, r, 0};if (l == r)return;int mid = (l + r) >> 1;build(lc, l, mid);build(rc, mid + 1, r);pushup(p);
}
void modify(int p, int x, LL k)
{int l = tr[p].l, r = tr[p].r;if (l == x && x == r){tr[p].mul = k;return;}int mid = (l + r) >> 1;if (x <= mid)modify(lc, x, k);elsemodify(rc, x, k);pushup(p);
}
LL query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if (x <= l && r <= y)return tr[p].mul;int mid = (l + r) >> 1;LL L = 1, R = 1;if (x <= mid)L = query(lc, x, y);if (y > mid)R = query(rc, x, y);// 分情况讨论合并区间:if (L == 0 || L == 1 || R == 0 || R == 1)return L * R;else if (L == M || R == M)return M;else if (L * R >= M)return M;elsereturn L * R;
}
int main()
{build(1, 1, 1e5);int Q;cin >> Q;while (Q--){LL op, x;cin >> op;if (op == 1){cin >> x;top++;modify(1, top, x);}else if (op == 2){if (top)top--;}else if (op == 3){cin >> x;if (top < x)cout << "ERROR" << endl;else{LL ret = query(1, top - x + 1, top);if (ret == M)cout << "OVERFLOW" << endl;elsecout << ret << endl;}}}return 0;
}

栈解法:

#include <iostream>
using namespace std;
typedef long long LL;
const LL INF = (1ull << 32);
const int N = 1e5 + 10;
struct node
{LL x, id;
}st1[N]; // ⾮ 0/1 元素
int st2[N]; // 0 元素
int top, t1, t2;
int main()
{int Q; cin >> Q;for (int i = 1; i <= Q; i++){LL op, x; cin >> op;if (op == 1) // 进栈{cin >> x;top++;if (x == 1) continue;else if (x == 0) st2[++t2] = top;else st1[++t1] = { x, top };}else if (op == 2) // 出栈{if (top == 0) continue;if (st2[t2] == top) t2--;else if (st1[t1].id == top) t1--;top--;}else // 查询{cin >> x;LL left = top - x + 1;if (x > top) cout << "ERROR" << endl;else if (t2 && st2[t2] >= left) cout << 0 << endl;else{LL ret = 1;int t = t1;while (t && st1[t].id >= left){ret = ret * st1[t].x;if (ret >= INF) break;t--;}if (ret >= INF) cout << "OVERFLOW\n";else cout << ret << endl;}}}return 0;
}

数列差分

【解法】贪心

调整操作和交换操作可以混着来,不影响最终结果。因此,我们可以随意调整两个数组的顺序。 将两个数组按照从小到大的顺序排序,为了能够使得操作次数最少,对于此时最小的 a[i] ,应该让其减去 b 数组中目前最小的元素 b[j] :

    • 如果 a[i] > b[j] ,那就匹配;

    • 如果 a[i] ≤ b[j],说明此时最小的 b[j] 都不能让 a[i] 减完之后变成正数。为了让 b[j] 物尽其用,调整 b 数组中的最大值,使其与 a[i] 匹配。

【代码参考】

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], b[N];
int main()
{cin >> n;for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= n; i++) cin >> b[i];sort(a + 1, a + 1 + n);sort(b + 1, b + 1 + n);int ret = 0;for (int i = 1, j = 1; i <= n; i++){if (a[i] > b[j]) j++;else ret++;}cout << ret << endl;return 0;
}

翻转硬币

【解法】动态规划

这里比较容易想到的是一个二维的动态规划:f[i][st] 表示第 i 行的翻转状态为 st  时,前 i 行能够获得的总价值。其中 st 为 0 表示没有反转,为 1 表示翻转。

又因为这道题的价值计算需要知道三行的状态,因此可以定义三维的状态表示,来表示当前这一行以及下一行的状态,从而计算出当前行能够获得的总价值。

1、状态表示: f[i][now][down] 表示:第 i 行的翻转状态为 now ,第 i + 1 行的翻转状态为 down 时,前 i 行能够获得的总价值。其中 now 和 down 为 0 表示没有反转,为 1 表示翻转。最终结果为 max(f[n][0][0], f[n][1][0]) 。

2、状态转移方程:

     ◦ 前一行不翻转:前 i − 1 行能够提供的价值为 f[i − 1][0][now] ;

     ◦ 前⼀行翻转:前 i − 1 行能够提供的价值为 f[i − 1][1][now] ;

根据前一行的状态,结合 now 和 down 就可以计算出当前行能够提供的价值 w 。

状态转移方程为:max(f[i − 1][0][now], f[i − 1][1][now]) + w 

【代码参考】

#include <iostream>
using namespace std;
const int N = 1010, M = 3;
int n, m;
char a[2][N][N];
int f[N][M][M];
int calc(char a[], char b[], char c[])
{int sum = 0;for (int i = 1; i <= m; i++){int t = 0;if (b[i] == b[i - 1]) t++;if (b[i] == b[i + 1]) t++;if (b[i] == a[i]) t++;if (b[i] == c[i]) t++;sum += t * t;}return sum;
}
int main()
{cin >> n >> m;for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++){cin >> a[0][i][j];if (a[0][i][j] == '0') a[1][i][j] = '1';else a[1][i][j] = '0';}for (int i = 1; i <= n; i++)for (int now = 0; now <= 1; now++)for (int down = 0; down <= 1; down++){int x = f[i - 1][0][now] + calc(a[0][i - 1], a[now][i], a[down][i + 1]);int y = f[i - 1][1][now] + calc(a[1][i - 1], a[now][i], a[down][i + 1]);f[i][now][down] = max(x, y);}cout << max(f[n][0][0], f[n][1][0]) << endl;return 0;
}

破解信息

【解法】贪心

字典序最大的回文子串,首字母一定是原字符串中字典序最大的字符,后续字符也应该是原字符串中字典序最大的字符。因此,字典序最大的回文子串应该只由最大的字符组成,只要中间加入一个其他字符,一定会让字典序变小。 统计一下原字符串中每一个字符出现,找出字典序最大字符出现的次数即可。

【代码参考】

#include <iostream>
using namespace std;
string s;
int cnt[26];
int main()
{cin >> s;for (auto ch : s) cnt[ch - 'a']++;for (int i = 25; i >= 0; i--)if (cnt[i]){while (cnt[i]--) cout << char(i + 'a');break;}return 0;
}

交互

【解法】差分约束

差分约束的模板题。这里先讲一下差分约束

【差分约束系统】

如果一个系统由 n 个变量以及x_{1},x_{2},x_{3}...x_{n}个约束条件组成,每个约束条件形如 x_{i}-x_{j}\leqslant k的不等式,则称其为差分约束系统。例如:

\left\{\begin{matrix} x_{3} - x_{1}\leqslant -1 \\ x_{2} - x_{1}\leqslant -2 \\ x_{2} - x_{3}\leqslant 4 \\ x_{4} - x_{5}\leqslant -5 \\ x_{4} - x_{3}\leqslant -2 \\ x_{3} - x_{4}\leqslant 2 \end{matrix}\right.

我们要解决的问题:

1、求出一组解 x1 = a1 , x2 = a2 , ..., xn = an ,使得所有的约束条件都成立;

2、如果无解,也需要判断出无解;

3、在更多的约束条件下,求出所有变量的最大值或者最小值。

【最短路径求可行解】

求解差分约束系统,可以将不等式组转换成图论的单源最短路(或者最长路)问题。

  • 当我们将所有的点距离源点的最短路求出来之后,对于 x → y 的这条权值为 w 的边,满足dist[y] ≤ dist[x] + w ;
  • 观察 xb ≤ xa + k ,类似于单源最短路中的三角不等式:dist[y] ≤ dist[x] + w ;
  • 因此,对于不等式  xb ≤ xa + k ,可以建立一条 a → b 权值为 k 的边。对于不等式组,就可以 创建出来一个图结构。然后在这个图上,来一次最短路算法。最终的 dist 数组,就是一组可行解。
  • 一般情况下,这个图结构是存在负边权的,基本上都是选择 bf 算法或者 spfa 算法求最短路。

以下面的例子为例

根据不等式组建图

以 1 号点为源点,跑⼀遍 spfa 算法可得一组解:x1 = 0,  x2 = −2,  x3 = −5,  x4 = −7 。

注意如果存在负环就说明无解

现在我们看题目,会得到一个结论:在[l,r]区间任取一个值减去[p,q]区间任意一个值,结果都是大于等于ans。设a[x]在[l,r]区间,a[y]在[p,q]区间,那么a[x]-a[y]\geqslant ans就可以转化为a[y]\leqslant a[x]-ans,就是一个差分约束,跑一遍spfa算法即可。

【代码参考】

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1e4 + 510, M = 2e5 + 10;
int n, m;
int h[N], e[M], w[M], ne[M], id;
bool st[N];
int dist[N], cnt[N];
void add(int a, int b, int c)
{id++;e[id] = b, w[id] = c, ne[id] = h[a], h[a] = id;
}
bool spfa()
{memset(dist, 0x3f, sizeof dist);queue<int> q;q.push(0);dist[0] = 0;st[0] = true;while (q.size()){int x = q.front(); q.pop();st[x] = false;for (int i = h[x]; i; i = ne[i]){int y = e[i], z = w[i];if (dist[x] + z < dist[y]){dist[y] = dist[x] + z;cnt[y] = cnt[x] + 1;if (cnt[y] >= n + m + 1) return true;if (!st[y]){q.push(y);st[y] = true;}}}}return false;
}
int main()
{cin >> m >> n;for (int i = 1; i <= m; i++){int l, r, p, q, x; cin >> l >> r >> p >> q >> x;// 中转点的编号 n + ifor (int j = l; j <= r; j++) add(j, n + i, 0);for (int j = p; j <= q; j++) add(n + i, j, -x);}for (int i = 1; i <= n; i++) add(0, i, 0);if (spfa()) cout << "No Solution" << endl;else{int minn = dist[1], maxn = dist[1];for (int i = 2; i <= n; i++){minn = min(minn, dist[i]);maxn = max(maxn, dist[i]);}cout << maxn - minn << endl;}return 0;
}

国赛【未完待续... ...】

http://www.dtcms.com/a/450595.html

相关文章:

  • 电商网站建设实训步骤wordpress视频采集
  • 哪些网站是做快消品的肇庆cms建站系统
  • 网站建设小组五类成员成都大型商城网站建设
  • 郑州优化网站公司长春餐饮网站建设
  • seo专业培训网络班济南网络优化厂家
  • 申请网站多少钱工作职责怎么写
  • 做网站花都区包头网站设计推广
  • 数据埋点指南
  • 2025-10-06 Python不基础 11——if 判断
  • AI智能体升级实战:从规则匹配到Function Call,准确率提升86%的技术选型之路
  • 威联通nas 做网站湖州市南浔区建设局网站
  • C47-数组指针
  • 品牌网站建设咨询新产品上市推广策划方案
  • 男和男做的视频网站网站被攻击如何处理
  • 石家庄seo关键词网站推广优化怎样
  • 卓越建站快车南充建设企业网站
  • MySQL删除数据后表空间处理
  • 在线学习建设网站宁波易通建设网站
  • 济南网站制作企业设计网站的步骤有哪些
  • LeetCode:96.只出现一次的数字
  • 我国空间站建造西安做网站推广
  • 算法竞赛补题1
  • 网站优化设计公司百度小程序平台
  • 衡水网站制作费用潜山做网站
  • 光全息|OAM-旋转双维度复用全息
  • 发布网站iis上报404错误网站建设的行业分析
  • 专业购物网站建设网站备案 互联网信息
  • 光通信|OAM-偏振并行(解)复用器
  • 企业级大模型部署
  • FreeRTOS与信号量(四)