【信息学做题日记】一道从紫降到绿的权值线段树引起的话题

文摘   2024-11-08 07:36   北京  

大家好!我是老码农。

昨天快9点30的时候,我呼叫小码匠,问下她题做得如何了?

小码匠说:还在调试呢?

我有点小急,这小丫头又杠上了吧。

题目调试的时候要讲究方式方法,很多时候需要跳出来再跳进去,更容易发现问题。

这个是小码匠写的代码。样例都没过。

接下来我跟她一起看了下,结果几分钟就搞定了。

我们先看有问题的原版代码:

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define ls u << 1
#define rs u << 1 | 1
const int maxn = 2e5 + 5;

struct line {
ll l, r, w;
} t[maxn << 2];

void push_up(int u) {
t[u].w = t[ls].w + t[rs].w;
}

void build(int u, int l, int r) {
t[u].l = l;
t[u].r = r;
if (l == r) {
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
push_up(u);
}

void modify(int u, int x, ll v) {
if (t[u].l == x && t[u].r == x) {
t[u].w += v;
return;
}
ll mid = (t[u].l + t[u].r) >> 1;
if (mid >= x) {
modify(ls, x, v);
} else {
modify(rs, x, v);
}
push_up(u);
}

int ask(int u, ll y) {
if (t[u].l == t[u].r) {
return t[u].l;
}
if (t[ls].w <= y) { // 我们实际上是不断的去寻找第一个小于y的值,所以如果左子树值更大,那么说明我们要找的值在左边
return ask(ls, y);
} else {
return ask(rs, y - t[ls].w); // 右边同理但是由于右子树的区间值都不包含左子树,所以要减去左边的值
}
}

int s[maxn];
struct student {
int h;
ll cnt;
} g[maxn];

void best_coder() {
int n;
cin >> n;
int cnt = 1;
for (int i = 1; i <= n; ++i) {
cin >> g[i].h >> g[i].cnt;
s[i] = g[i].h;
}
sort(s + 1, s + 1 + n);
int k = unique(s + 1, s + 1 + n) - s - 1;
for (int i = 1; i <= n; ++i) {
int idx = lower_bound(s + 1, s + 1 + k, g[i].h) - s;
g[i].h = idx;
}
build(1, 1, n);
for (int i = 1; i <= n; ++i) {
modify(1, g[i].h, g[i].cnt);
ll sum = t[1].w;
if (sum % 2 == 1) {
sum = sum / 2 + 1;
} else {
sum = sum / 2;
}
cout << s[ask(1, sum)] << '\n';
}
}

void happy_coder() {

}

int main() {
// 提升cin、cout效率
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

// 小码匠
best_coder();

// 最优解
// happy_coder();

return 0;
}

几个重要问题

我看了小码匠的代码,其实我是不知道哪错了的。只是凭自己的经验引导她。

首先:判断问题可能出现的点

那如何判断呢,我们先看主函数

主函数主要是best_coder这个函数,如果分析这个函数

  • 第2行~第8行:读入数据;
  • 第9行~第14行:离散化;
  • 第15行:构建线段树;
  • 第16行~第25行:这段主体的逻辑不复杂,核心是在modify()ask()这个函数;
void best_coder() {
int n;
cin >> n;
int cnt = 1;
for (int i = 1; i <= n; ++i) {
cin >> g[i].h >> g[i].cnt;
s[i] = g[i].h;
}
sort(s + 1, s + 1 + n);
int k = unique(s + 1, s + 1 + n) - s - 1;
for (int i = 1; i <= n; ++i) {
int idx = lower_bound(s + 1, s + 1 + k, g[i].h) - s;
g[i].h = idx;
}
build(1, 1, n);
for (int i = 1; i <= n; ++i) {
modify(1, g[i].h, g[i].cnt);
ll sum = t[1].w;
if (sum % 2 == 1) {
sum = sum / 2 + 1;
} else {
sum = sum / 2;
}
cout << s[ask(1, sum)] << '\n';
}
}

void happy_coder() {

}

int main() {
// 提升cin、cout效率
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

// 小码匠
best_coder();

// 最优解
// happy_coder();

return 0;
}

看完这段代码后,我问了小码匠几个问题?

  • 第1点:离散化那里能确定一定没问题嘛?

    答曰:没问题;

  • 第2点:构建线段树能确定一定没问题嘛?

    答曰:那段代码我静态检查过,处理简单,不可能写挂;

  • 第3点:你要不要把离散化和构建线段树的值输出来确认下,有没有问题?

    答曰:确认过了,没问题;

  • 第4点:那你接下来就应该重点确认modify()ask()这个函数;

  • 第5点:你最好重点确认入参和条件判断是否写反了。

直插重点

小码匠瞪着代码,看了几分钟modify()ask()这两个函数

int ask(int u, ll y) {
if (t[u].l == t[u].r) {
return t[u].l;
}
if (t[ls].w <= y) {
return ask(ls, y);
} else {
return ask(rs, y - t[ls].w);
}
}

然后随手修改了第5行,从<=改成>=,3个样例顺利通过。

然后我接着跟她说,为什么这个符号打错了呢?

  • 是思路错了?
  • 还是梳理的有遗漏?

后来我跟小码匠说:对于一些复杂的逻辑或者需要分类讨论的,最好加上注释说明,加注释说明的时候无形中是有助于你的理解和梳理分支。

然后这段代码又稍微改了点。成这个样子了。

int ask(int u, ll y) {
if (t[u].l == t[u].r) {
return t[u].l;
}
if (t[ls].w >= y) { // 我们实际上是不断的去寻找第一个小于y的值,所以如果左子树值更大,那么说明我们要找的值在左边
return ask(ls, y);
} else {
return ask(rs, y - t[ls].w); // 右边同理但是由于右子树的区间值都不包含左子树,所以要减去左边的值
}
}

划重点:很多时候一旦陷进去,就如同走了个死胡同。此时,最好的方式,放松下或转移下注意力,让大脑短暂离开代码。

此路不通,龙马精神一下,回来换条路就通顺了,条条大路通罗马。

再说标签

别太迷信洛谷的标签,洛谷标签太不靠谱了。

这道题估计最早的标签是紫题,实际也就上位绿,但每个人的感受不同。

有人说也就黄题,这个还是有点过了,线段树模版都是绿题,大佬们也不能太自谦了,莫非已成大佬就忘记当年啃模版那痛苦尽头。

有时候看洛谷评论区也挺逗的,经常就吵起来了。

没有必要去纠结这些,最重要学到知识就行了。

     
010

好啦,今天就写道这里。



小码匠和老码农
我是小码匠,一名初二的女生,本来可以享受那么多的休息时光,却偏偏要奉献给一方键盘和数学公式。 未来无论风雨挫折,作为一个OIer,源于喜欢,无怨无悔。 在这里我会不定期分享一些数学和信息学题目,期待与你共同进步。
 最新文章