首页
关于作者
靶场
Search
1
河南萌新联赛2026第(一)场:河南工业大学A题题解
19 阅读
2
注意!
18 阅读
3
河南萌新联赛2026第(一)场:河南工业大学E题题解
12 阅读
4
河南萌新联赛2026第(一)场:河南工业大学B题题解
11 阅读
5
sql注入
10 阅读
默认分类
游戏
比赛
网安
学习
登录
Search
llwqs
累计撰写
9
篇文章
累计收到
2
条评论
首页
栏目
默认分类
游戏
比赛
网安
学习
页面
关于作者
靶场
搜索到
5
篇与
的结果
2026-07-28
河南萌新联赛2026第(一)场:河南工业大学J题题解
这道题的核心在于理解“交替共鸣序列”的定义,并找出删除哪一个元素后,剩下的序列能满足这个定义。 题目地址解题思路1.理解“交替共鸣序列”:* 题目定义:序列中任意两个相邻元素的奇偶性都不同。 * 通俗理解:序列的奇偶性必须是交替出现的,例如 `奇, 偶, 奇, 偶` 或 `偶, 奇, 偶, 奇`。 * 特例:长度为 0 或 1 的序列天然满足条件。 2.分析删除操作:* 我们需要尝试删除序列中的每一个元素,然后检查剩下的序列是否是“交替共鸣序列”。 * 直接模拟删除并检查的时间复杂度是 O(n²),对于 n ≤ 10⁶ 的数据规模会超时。我们需要一个 O(n) 的线性解法。 3.寻找高效方法:* 一个序列如果不是“交替共鸣序列”,那一定是因为在某个位置 `i`,`a[i]` 和 `a[i+1]` 的奇偶性相同。我们称这个位置为“冲突点”。 * 当我们删除一个元素 `a[k]` 时,只会影响 `a[k-1]` 和 `a[k+1]` 之间的关系。序列中其他所有相邻元素的奇偶关系都保持不变。 * 因此,我们可以先遍历一遍原数组,找出所有“冲突点”的位置。 * 然后,对于每一个可能的删除位置 `k`,我们只需要检查: * 删除 `a[k]` 是否会消除原有的冲突? * 删除 `a[k]` 是否会引入新的冲突(即 `a[k-1]` 和 `a[k+1]` 的奇偶性是否相同)? * 如果删除 `a[k]` 后,整个序列不再有任何冲突,那么 `k` 就是一个合法的删除位置。 4.算法步骤:* **预处理**:遍历数组,用一个数组 `conflicts` 记录所有冲突点的位置。`conflicts[i]` 为 `true` 表示 `a[i]` 和 `a[i+1]` 奇偶性相同。 * **统计总冲突数**:计算 `conflicts` 数组中 `true` 的个数,记为 `total_conflicts`。 * **遍历删除位置**:对于每个位置 `k` (从 0 到 n-1): * 计算删除 `a[k]` 会消除的冲突数 `removed_count`。这包括 `conflicts[k-1]` (如果 `k>0`) 和 `conflicts[k]` (如果 `k<n-1`)。 * 计算删除 `a[k]` 会引入的新冲突数 `added_count`。这只发生在 `k>0` 且 `k<n-1` 时,检查 `a[k-1]` 和 `a[k+1]` 的奇偶性。 * 如果 `total_conflicts - removed_count + added_count == 0`,则说明删除 `a[k]` 后序列是“交替共鸣序列”,答案加一。完整代码#include<bits/stdc++.h> using namespace std; bool panduan(long long a, long long b) { return (a & 1) == (b & 1); } void solve() { int n; cin >> n; vector<long long> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } if (n == 1) { cout << 1 << endl; return; } vector<bool> conflicts(n - 1, false); int total_conflicts = 0; for (int i = 0; i < n - 1; ++i) { if (panduan(a[i], a[i + 1])) { conflicts[i] = true; total_conflicts++; } } int ans = 0; for (int k = 0; k < n; ++k) { int removed_count = 0; int added_count = 0; if (k > 0 && conflicts[k - 1]) { removed_count++; } if (k < n - 1 && conflicts[k]) { removed_count++; } if (k > 0 && k < n - 1) { if (panduan(a[k - 1], a[k + 1])) { added_count++; } } if (total_conflicts - removed_count + added_count == 0) { ans++; } } cout << ans << endl; } int main() { int t; cin >> t; while (t--) { solve(); } return 0; }
2026年07月28日
8 阅读
0 评论
0 点赞
2026-07-28
河南萌新联赛2026第(一)场:河南工业大学G题题解
核心思路:逆向思维与差分思想这道题表面上是一个复杂的“区间修改”问题,但如果我们转换视角,将其逆向思考,就会变得非常直观。 题目链接1. 逆向思维:从“削减”到“堆叠”题目要求将一排参差不齐的树全部减为 0,每次操作可以对一段连续的树高度减 1。由于减法和加法是完全可逆的,我们可以把这个问题等价转换为:初始时所有树的高度都是 0,最少需要多少次“对一段连续的树高度加 1”的操作,才能恰好拼凑出题目给定的目标高度?2. 直观想象:画山峰想象我们在一张白纸上画一排柱子(山峰)。当我们要画第一棵树(高度为 $a_0$)时,我们必须从它开始,连续发起 $a_0$ 次加 1 的操作。当画到第二棵树(高度为 $a_1$)时,为了操作次数最少,我们会尽量让之前的操作“顺带”覆盖过来。如果 $a_1 > a_0$,说明之前延续过来的操作不够用,我们必须额外发起 $a_1 - a_0$ 次仅从第二棵树开始的新操作。如果 $a_1 \le a_0$,说明之前延续过来的操作已经足够覆盖当前树,甚至还会有多余的操作在这里自然“结束”,我们完全不需要增加新的操作次数。3. 提炼规律通过上述过程,我们可以得出一个普适的规律:只有当当前位置的树比前一棵树高时,我们才需要发起新的操作。新发起的操作次数,恰好等于 当前高度 - 前一个高度。如果当前树比前一棵树矮或一样高,则不需要增加操作次数。因此,整个问题的答案,就是遍历数组,累加所有“上升沿”的高度差。完整代码#include<bits/stdc++.h> using namespace std; int main(){ int n; cin>>n; vector<long long> a(n); for(int i=0;i<n;i++){ cin>>a[i]; } long long ans=0; long long prev=0; for(int i=0;i<n;i++){ if(a[i]>prev){ ans+=(a[i]-prev); } prev=a[i]; } cout<<ans<<endl; return 0; }
2026年07月28日
9 阅读
0 评论
0 点赞
2026-07-27
河南萌新联赛2026第(一)场:河南工业大学E题题解
题目链接问题重述与核心转化给定一个长度为 n 的整数序列 a,以及两个整数 K 和 D。我们需要找出有多少个不同的连续子区间 [l, r],同时满足以下两个条件:种类数限制:区间内不同数字的个数恰好为 K。极差限制:区间内最大值与最小值的差(即极差)不超过 D。直接计算“恰好包含 K 种不同数字”的区间数量逻辑较为复杂。这里我们引入容斥原理,将问题巧妙转化:恰好包含 K 种 = 至多包含 K 种 - 至多包含 (K-1) 种这样,我们将一个复杂问题分解为两个结构完全相同、更容易解决的子问题。接下来,我们只需解决一个通用子问题:“计算不同数字种类数至多为 limit_k,且极差不超过 D 的子区间数量”。核心算法:滑动窗口与单调性分析对于上述子问题,我们可以使用滑动窗口(双指针)算法来高效求解。滑动窗口算法之所以能够成立且高效,根本原因在于指针单调移动时,窗口内部状态(不同值数量、极值)具有严格的单调变化趋势。1. 端点移动与“不同值数量”的单调性右指针 right 向右移动(窗口扩张):新元素进入窗口,窗口内不同值的数量 size 只会增加或保持不变,绝对不会减少。左指针 left 向右移动(窗口收缩):元素离开窗口,窗口内不同值的数量 size 只会减少或保持不变,绝对不会增加。2. 端点移动与“极值(最大值、最小值)”的单调性右指针 right 向右移动(窗口扩张):新加入的元素可能比当前最大值还大,也可能比当前最小值还小。因此,最大值可能变大(绝不会变小),最小值可能变小(绝不会变大)。综合来看,极差(最大值 - 最小值)可能变大,也可能保持不变。左指针 left 向右移动(窗口收缩):被移出的元素如果恰好是窗口内唯一的最大值或最小值,极值会发生改变。因此,最大值可能变小(绝不会变大),最小值可能变大(绝不会变小)。综合来看,极差可能变小,也可能保持不变。3. 单调性对算法的支撑理解了上述趋势,滑动窗口的逻辑便顺理成章:触发收缩:当右指针 right 向右移动一步后,由于扩张带来的单调性,种类数或极差可能变大,导致窗口变得“不合法”(即 size > limit_k 或 极差 > D)。安全收缩:因为左指针 left 向右移动时,种类数和极差都呈现单调递减(或不变)的趋势,所以我们可以放心地不断向右移动 left,直到窗口重新回到合法状态,这样也就意味着我们可以轻松的统计 至多 的问题。结果统计:因为 left 是单调递增的,对于当前的 right,我们找到的 left 一定是满足条件的最左边界。这也保证了以 right 为右端点的合法区间数量恰好是 right - left + 1,不会漏算也不会多算。算法流程基于上述单调性,算法的具体执行流程如下:初始化左指针 left = 0,并使用一个有序容器(如 map,在map里的大小就是不同值的数量,并且map是有序的可以节约我们的极值查询时间)维护当前窗口内的数字及其出现次数。遍历右指针 right 从 0 到 n-1,将 a[right] 加入窗口。检查当前窗口是否合法。如果不合法,则不断将 a[left] 移出窗口并右移 left,直到窗口重新满足“种类数 ≤ limit_k 且 极差 ≤ D”的条件。当窗口合法时,累加当前右端点对应的合法子区间数量:count += (right - left + 1)。最终,通过 atMostK(K) - atMostK(K-1) 得到最终答案。易错点与注意事项1.limit_k < 0 的边界检查:当 K=0 时,会调用 atMostK(n, -1, D)。必须在此处进行边界拦截并直接返回 0,否则会导致后续逻辑出错。2.空容器访问:在计算极差时,必须先判断窗口是否为空(!window.empty())。对空容器调用获取最大/最小值的操作会导致程序崩溃。3.数据类型溢出: 结果计数:子区间的数量可能非常大,必须使用 long long 来存储结果。 极差计算:题目中的灵能值范围可能很大,两个 int 相减的结果也可能超出 int 范围。在计算极差时,必须先将其转换为 long long 再进行减法运算。4.容器的更新与删除:当左指针移动时,如果某个数字的计数减为 0,必须从容器中彻底删除该键值对。否则,容器的大小将无法正确反映窗口内不同数字的真实种类数。5.注意多组测试数据完整代码#include <iostream> #include <vector> #include <map> using namespace std; const int MAXN = 500005; int a[MAXN]; long long atMostK(int n, int limit_k, int D) { if (limit_k < 0) return 0; long long count = 0; int left = 0; map<int, int> window; for (int right = 0; right < n; right++) { window[a[right]]++; while (window.size() > (size_t)limit_k || (!window.empty() && (long long)window.rbegin()->first - window.begin()->first > D)) { int val_to_remove = a[left]; auto it = window.find(val_to_remove); if (it != window.end()) { it->second--; if (it->second == 0) { window.erase(it); } } left++; } count += (right - left + 1); } return count; } int main() { ios::sync_with_stdio(false); cin.tie(0); int T; if (!(cin >> T)) return 0; while (T--) { int n, K, D; cin >> n >> K >> D; for (int i = 0; i < n; ++i) { cin >> a[i]; } long long ans = atMostK(n, K, D) - atMostK(n, K - 1, D); cout << ans << "\n"; } return 0; }
2026年07月27日
12 阅读
0 评论
0 点赞
2026-07-27
河南萌新联赛2026第(一)场:河南工业大学B题题解
题目核心思路解析 这里是题目链接1. 操作的本质与等差数列的性质题目允许我们对数组中的元素进行任意次“加 $x$”或“减 $x$”的操作。这意味着,最终数组中的每个元素 $a_i$ 都可以变成 $a_i + k \cdot x$($k$ 为任意整数)。换句话说,每个元素在模 $x$ 意义下的余数是不会改变的。假设我们最终将数组变成了一个公差为 $D$ 的等差数列,那么对于任意相邻的两个元素,它们的差值在模 $x$ 意义下必须等于 $D$。即:$a_{i+1} - a_i \equiv D \pmod x$。2. 推导关键约束条件既然所有的相邻差值 $a_{i+1} - a_i$ 在模 $x$ 意义下都等于 $D$,那么任意两个相邻差值在模 $x$ 意义下必须相等。即:$(a_{i+1} - a_i) \equiv (a_{j+1} - a_j) \pmod x$。将其转化为整除关系,即:$x$ 必须能整除任意两个相邻差值的差。即:$x \mid ((a_{i+1} - a_i) - (a_{j+1} - a_j))$。为了让 $x$ 尽可能大,我们需要找到所有“相邻差值的差”的最大公约数(GCD)。完整代码#include<bits/stdc++.h> using namespace std; long long my_gcd(long long a, long long b) { a = abs(a); b = abs(b); while (b != 0) { a %= b; swap(a, b); } return a; } int main() { int n; if (!(cin >> n)) return 0; vector<long long> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } bool is_arithmetic = true; if (n >= 2) { long long diff = a[1] - a[0]; for (int i = 2; i < n; ++i) { if (a[i] - a[i-1] != diff) { is_arithmetic = false; break; } } } if (is_arithmetic) { cout << -1 << endl; return 0; } long long base_diff = a[1] - a[0]; long long gcd_val = 0; for (int i = 2; i < n; ++i) { long long current_diff = a[i] - a[i-1]; long long val = current_diff - base_diff; gcd_val = my_gcd(gcd_val, val); } cout << gcd_val << endl; return 0; }
2026年07月27日
11 阅读
0 评论
1 点赞
2026-07-27
河南萌新联赛2026第(一)场:河南工业大学A题题解
这道题是一道经典的模拟题,考察的是对复杂规则的理解和代码实现能力。 题目地址题目解析这道题要求我们模拟一个在环形棋盘上进行的多玩家大富翁游戏。我们需要根据给定的规则,一步步执行每个玩家的回合,直到游戏结束。 非常非常大的令人恶心的模拟核心规则梳理游戏环境:一个有 m 个格子的环形棋盘,编号 1 到 m。第 m 格的下一格是第 1 格。n 名玩家,初始都在第 1 格,拥有各自的初始金币。回合流程:玩家按编号 1 到 n 的顺序循环行动。停牌状态: 如果玩家处于停牌状态(由监狱格导致),则解除停牌,本回合不移动、不使用骰子。正常移动: 否则,使用下一个骰子点数 d,顺时针移动 d 格。触发效果: 移动后,根据落点格子的类型触发相应效果。格子类型与效果:起点格 (第 1 格): 落在该格获得 200 金币。注意:游戏开始时在第 1 格不触发此效果。地产格:无主: 如果玩家金币足够,必须购买(扣除价格,玩家成为地主)。有主 (且地主不是自己): 需要向地主支付过路费。如果金币不足,支付所有金币并破产。自己所有: 无效果。幸运格: 获得 150 金币。惩罚格: 扣除 100 金币。如果金币不足,扣除所有金币并破产。监狱格: 进入停牌状态(下一回合不行动)。破产规则:当需要支付金币(过路费或惩罚)时,如果当前金币不足以支付,则支付所有剩余金币,金币变为 0,并立即破产。如果金币恰好足够,支付后金币变为 0,但不会破产。玩家破产后:金币为 0,其所有地产变为无主,并永久退出游戏(不再参与后续回合)。游戏结束条件:场上仅剩 1 名玩家未破产。所有骰子都已使用完毕。输出:游戏结束时,按玩家编号顺序输出所有玩家的最终状态(金币数量)。解题思路这是一个纯粹的模拟问题,关键在于准确地将题目规则转化为代码逻辑。数据结构设计:玩家 (Player): 需要一个结构体来存储每个玩家的状态,包括:当前金币、当前位置、是否破产、是否停牌、拥有的地产列表。棋盘 (Board): 需要一个数组或向量来表示棋盘上的每个格子。每个格子需要存储其类型和相关信息(如地产的价格、过路费、地主ID)。骰子 (D): 一个数组或向量,按顺序存储所有骰子点数。模拟主循环:使用一个循环来模拟游戏回合。在循环内部,遍历所有玩家(从 1 到 n)。对于每个玩家,首先检查其是否已破产,如果破产则跳过。然后检查游戏是否应该结束(只剩一个玩家或骰子用完)。如果玩家未破产且游戏未结束,则执行该玩家的回合:处理停牌状态。如果未停牌,则掷骰子、移动、触发落点效果。在处理效果时,需要仔细处理金币的增减和破产判断。完整代码#include<bits/stdc++.h> using namespace std; enum celltype{ start, grounds, lucky, punish, jali, }; struct ground{ long long price; long long toll; int owner_id; ground(long long p, long long t) : price(p), toll(t), owner_id(-1) {} };//地产格的相关信息 struct cell{ celltype type; ground* str; cell() : type(start), str(NULL) {} //特别的,防止因为后面vector数组的空建立而报错 cell(celltype t) : type(t), str(NULL) {} cell(ground* p) : type(grounds), str(p) {}//处理地产格 };//处理棋盘格子的属性 struct Player{ int id; long long coins; int pos; bool is_broke;//是否破产 bool is_ban;//是否停牌 vector<int> groundd; Player(int i, long long c) : id(i), coins(c), pos(1), is_broke(false), is_ban(false) {} };//处理玩家属性 int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); int n, m; cin >> n >> m; vector<Player> players; for(int i = 1; i <= n; i++){ long long ci; cin >> ci; players.emplace_back(i, ci); } vector<cell> board(m + 1); for(int i = 1; i <= m; i++){ int types; cin >> types; if(types == 0){ board[i] = cell(start); } else if(types == 1){ long long p, t; cin >> p >> t; board[i] = cell(new ground(p, t)); } else if(types == 2){ board[i] = cell(lucky); } else if(types == 3){ board[i] = cell(punish); } else if(types == 4){ board[i] = cell(jali); } } int k; cin >> k; vector<long long> d(k); for(int i = 0; i < k; i++){ cin >> d[i]; } int d_i = 0; int player_nobroke = n; while(d_i < k && player_nobroke > 1){ for(int i = 0; i < n; i++){ Player& player = players[i]; if(player.is_broke) continue; if(d_i >= k || player_nobroke <= 1) break; if(player.is_ban){ player.is_ban = false; } else { long long ds = d[d_i++]; long long new_pos = (long long)player.pos + ds; player.pos = (int)((new_pos - 1) % m + 1); cell& current_cell = board[player.pos]; switch(current_cell.type){ case start: player.coins += 200; break; case grounds: { ground* str = current_cell.str; if(str->owner_id == -1){ if(player.coins >= str->price){ player.coins -= str->price; str->owner_id = player.id; player.groundd.push_back(player.pos); } } else if(str->owner_id != player.id){ long long t = str->toll; if(player.coins < t){ players[str->owner_id - 1].coins += player.coins; player.coins = 0; player.is_broke = true; player_nobroke--; for(int str_pos : player.groundd){ board[str_pos].str->owner_id = -1; } player.groundd.clear(); } else { player.coins -= t; players[str->owner_id - 1].coins += t; } } break; } case lucky: player.coins += 150; break; case punish: if(player.coins < 100){ player.coins = 0; player.is_broke = true; player_nobroke--; for(int str_pos : player.groundd){ board[str_pos].str->owner_id = -1; } player.groundd.clear(); } else { player.coins -= 100; } break; case jali: player.is_ban = true; break; } if(d_i >= k || player_nobroke <= 1) break; } } } for (int i = 0; i < n; ++i) { if (players[i].is_broke) { cout << "bankrupt 0"; } else { cout << players[i].coins << " " << players[i].groundd.size(); } if (i < n - 1) cout << "\n"; } cout << endl; for (int i = 1; i <= m; ++i) { if (board[i].type == grounds){ delete board[i].str; } } return 0; }
2026年07月27日
19 阅读
2 评论
1 点赞