国庆七日总结

Day 1

分数:100+0+0+5

T2:社交距离S

错误原因:

十年OI一场空,不开longlong见祖宗(自测时开longlong了,但提交时交错代码了。。。)

思路:

因为 N,M \le 1e5,所以时间复杂度最高可以为 O(n log n),又因为没有两个区间有重合部分或在端点处相交,所以可以将所有区间先排序,该题具有单调性,所以可以 D 的值(我的二分 mid 没开 long long !)

T3:拖延症小信

错误原因:考试时没时间做啦!!!

思路:

1. 状态定义:

  • dp[i][j][0]:考虑前 i 个任务、当前时间为 j 时,能获得的最大总收益
  • dp[i][j][1]:对应最大收益下,被选择的任务个数

2.初始化:

  • 默认0

3.边界:

  • 遍历每个任务 i (从 1n ),并逆序遍历时间 j

4.状态转移:

  • 情况 1:选择第 i 个任务
    需要满足时间约束:当前时间 j 足够容纳任务耗时(j >= node[i].t),且任务结束时刻 < 截止时间j < node[i].d,因为任务从 j - node[i].t 开始,到 j 结束)
dp[i][j][0] = dp[i-1][j][0];
dp[i][j][1] = dp[i-1][j][1];
  • 情况 2:不选择第 i 个任务
    此时,“考虑前 i 个任务、时间 j” 的状态,等同于 “考虑前 i-1 个任务、时间 j” 的状态:
dp[i][j][0] = dp[i-1][j][0];
dp[i][j][1] = dp[i-1][j][1];

5.答案:

  • 遍历所有 i (任务数)和 j (时间),找到 dp[i][j][0] 的最大值(即最大总收益 ans ),以及对应 dp[i][j][1] (即选择的任务个数 res)

T4:世界树上世界果

错误原因:考试时BFS没写出来,只能骗分了。。。

思路:

  • 读入 n-1 条边 u, v ,双向添加到邻接表中,并更新两点的度数(l[u]++, l[v]++
  • 初始化队列:遍历所有点,将度数为 1 的特异点标记为已访问(vis[i]=1),并加入队列 q(这些是 BFS 的起点)。
  • BFS 扩展:从队列中的特异点出发,逐层扩展邻居。对于每个邻居 now_v,若未被访问过,则标记为已访问(vis[now_v]=1),并记录其到最近特异点的距离(p[now_v] = p[now_u] + 1),然后将 now_v 入队。
  • 遍历所有点,维护两个变量:k(记录最大距离)、cnt(记录最大距离的点的数量)。
  • 若当前点的距离 p[i] == k,则 cnt++
  • p[i] > k,则更新 k = p[i],并重置 cnt = 1(找到更大的距离,重新计数)。
  • 最后输出最大距离的点的数量 cnt,以及所有满足 p[i] == k 的点(按编号从小到大输出)

Day 2

分数:10+25+0+20

T1:子序列

错误原因:怕不加剪枝会TLE,所以加了几个剪枝WA了,结果不加剪枝就AC了

思路:

  • 通过排序 查找 “最小值、最大值、中位数” ,再枚举可能的最小 / 最大值对 ,结合二分查找 定位中位数,最终统计最长符合条件的子序列长度。

T2:LDS

错误原因:考试时结论没推出来,写了个dp骗分

思路:

  • 题目中排列满足 max(p_i, p_{i+1}) > p_{i+2} ,这保证了不存在长度 \ge 3 的严格下降子序列 。原因如下:假设存在长度为 3 的下降子序列 a > b > ca, b, c 位置递增),则中间位置 b 需满足 max(a_{b-1}, a_b) > a_{b+1} 。若 a_{b-1}=a`a_b=ba_{b+1}=c ,则 max(a, b)=a > c 成立,但此时会推导出更长的下降序列,与题目条件的约束矛盾。因此,所有子区间的 LDS 长度最多为 2

  • 所有子区间的 “基础长度”( LDS 至少为 $1$)
    所有子区间的数量为 1n 的。每个子区间的 LDS 至少为 1,因此这部分是 “基础总和”

  • LDS 长度为 2 的子区间的 “额外贡献”
    对于每一对相邻元素 (i, i+1),若 a[i] > a[i+1](即存在相邻下降对),则所有包含这对元素的子区间的 LDS 长度会从 1 提升到 2,因此每个这样的子区间需额外贡献 1。

  • 含这对元素的子区间数量为 i * (n - i)

  • l 的取值范围是 [1, i](共 i 种可能),保证左端点不超过 i

  • r 的取值范围是 [i+1, n](共 n - i 种可能),保证右端点不小于 i+1

  • 总和 = 所有子区间数量 + 相邻下降对的额外贡献总和

T3:涂鸭

错误原因:赛中没做!!!暴力好像可以骗好多分

思路:

  • 反向处理的核心逻辑
    因为后面的涂色操作会覆盖前面的操作,所以从最后一次操作倒序处理,能优先保留 “最新(后面)的操作” 对格子的影响,跳过 “已被后续操作覆盖” 的行 /或列。
  • 若操作是 “涂行”:检查该行 y_i 是否未被后续操作覆盖 (!visy_{y_i}) 。若未被覆盖,则该行的所有 w 个格子会被当前颜色覆盖(因为后续没有更晚的操作覆盖它了)。标记该行为 “已覆盖” (visy_{y_i} = 1),并更新已覆盖行的数量 cnt_y++。
  • 涂列同理
  • 统计最终颜色(包括初始颜色 0
    所有操作处理完后:
    非零颜色:收集所有 ans[color] > 0 的颜色,按升序排序后输出。
    初始颜色 0:若所有被覆盖的格子数之和 sum 小于总格子数 h \times w,则剩余格子为初始颜色 0,数量为 h \times w - sum

T4:小信的奇异背包

错误原因:DP 没想到最优方法,TLE

思路:

1. 状态定义:

  • dpp[i][j]:考虑前 i 个物品(物品编号从 1i),总价钱不超过j时,能获得的最大价值。
  • dps[i][j]:考虑后 i 个物品(物品编号从 in),总价钱不超过j时,能获得的最大价值

2.初始化:

  • 默认0

3.边界:

  • 当总价钱上限j = 0时,无法选任何物品,价值为 0dpp[i][0] = 0dps[i][0] = 0)。
  • 当物品数量为 0 时(前 0 个或后n+1个),价值为 0

4.状态转移:

  • 前向 DPdpp数组)
    对第i个物品(价钱x,价值y,限购z),枚举购买次数k0 ≤ k ≤ z,且 k \times x ≤ j
dpp[i][j] = max(dpp[i][j], dpp[i - 1][j - k * x] + k * y);

含义:前i 个物品的最优解,是 “不选第i 个物品(继承前i-1 个的解dpp[i-1][j] )” 与 “选k 个第i 个物品(前i-1 个用j-kx 价钱,加上k 个的价值ky )” 的最大值

  • 后向 DPdps数组)
    对第i个物品(价钱x,价值y,限购z),枚举购买次数k0 ≤ k ≤ z ,且 k\times x ≤ j
dps[i][j] = max(dps[i][j], dps[i + 1][j - k * x] + k * y);

含义:后i 个物品的最优解,是 “不选第i 个物品(继承后i+1 个的解dps[i+1][j] )” 与 “选k 个第i 个物品(后i+1 个用j-kx 价钱,加上k 个的价值ky )” 的最大值

5.答案:

  • 对于每个询问(去掉第d个物品,总价钱上限e):整体物品被拆分为 “前 d 个物品”和“后 d+2 个物品”(跳过第d+1个物品,即被去掉的物品)。枚举前半部分用的价钱j(0 ≤ j ≤ e),则后半部分可用价钱为e - j。总价值为 “前 d 个用j的最大价值(dpp[d][j])” 加 “后 d+2 个用e-j的最大价值(dps[d+2][e-j])”。最终答案为:\max\left \{ dpp_{d,j} + dps_{e +2,d-j} \Big | 0 \le e \le j\right \}

Day 3

分数:100+60+100+8

从Day3开始,DP没错过!

T2:LR String

思路:

  • 预处理:记录每个位置的 “最近 L/R 位置”
    定义数组 nextl_inextr_i
    nextl_i:字符串 s 中位置 i 及其左侧最近的 L 的位置。
    nextr_i:字符串 s 中位置 i 及其左侧最近的 R 的位置。
    预处理逻辑(从右往左遍历 s):
    维护变量 LR,记录当前遍历到的最近 LR 的位置。
    遍历到 s_i 时,若 s[i] == 'L',则更新 L = i;若 s[i] == 'R',则更新 R = i
    最终 nextl[i] = Lnextr[i] = R.
    预处理后,可快速查询 “某个位置附近的 L/R 位置”,为后续删除操作的模拟提供支持
  • 快速排除 不可能 的情况
    对每个查询字符串 t,先通过首尾特性快速排除无效情况:
    如果 st 长度相同但内容不同 → 直接输出 NO
    如果 s 开头是 L,但 t 开头是 R → 无法生成(s 开头的 L 只能删左侧,无法得到开头为 R 的 t)。
    如果 t 结尾是 L,但 s 结尾是 R → 无法生成(s 结尾的 R 只能删右侧,无法得到结尾为 Lt
  • 通过函数 check(s, t) 详细检查 t 是否可由 s 生成:
    指针 i 遍历 t 的每个字符,指针 j 遍历 s 的位置(模拟删除后剩余的位置)。
    t[i] 的每个字符:
    t[i] == 'L':需在 s 中从 j 位置找最近的 'L'(通过 nextl[j])。若找不到(nextl[j] == -1),说明无法生成,返回 false;否则 j 跳到 nextl[j] + 1(模拟删除该 'L' 左侧字符后的位置)。
    t[i] == 'R':需在 s 中从 j 位置找最近的 'R'(通过 nextr[j])。若找不到(nextr[j] == -1),说明无法生成,返回 false;否则 j 跳到 nextr[j] + 1(模拟删除该 'R' 右侧字符后的位置)。
    若 t 的所有字符都能通过上述步骤匹配,则返回 true,否则返回 false

T4:回文串

思路:

  • 统计与寻找 第 k 小的数
    用数组 b 统计每个数的出现次数。
    对 b 做前缀和,找到第一个满足 b[i] > k-1 的数 mid, 即序列中第 k 小的数(因为前缀和表示 “小于等于 i 的数的总个数”,第 k 小的数会让前缀和首次超过 k-1
  • 将原序列中小于等于 mid 的数存入数组 c(这些数是后续构造回文的核心候选,因为 mid 可通过操作删除,再将c排序
  • 双指针匹配回文串
    用左右指针 l(左端点)和 r(右端点)遍历 c,模拟回文构造:
    情况 1:c[l] == c[r]
    这两个数可作为回文的 “两端”,匹配成功。若 l != r,则计数 cnt += 2(两个数匹配);否则 cnt += 1(中间数自身匹配)。然后 l++,r--,继续匹配内部区间。
    情况 2:c[l] != c[r]
    尝试利用 “mid 可被删除” 的特性调整:
    c[l] == mid:左指针右移(模拟 “删除左数 mid”,通过操作选包含该 mid 的长区间,删去区间的第 k 小数 mid)。
    c[r] == mid:右指针左移(同理,删除右数 mid)。
    否则:无法通过操作调整使这两个数匹配,直接输出 NO
  • 若匹配的总数 ·cnt >= k-1·,说明能通过操作(删除多余元素)留下足够的匹配数,从而构造回文,输出 YES;否则输出 NO

Day 4

分数:100+90+60+100

T2:序列扩展

错误原因:0是肯定会被凑出来的,所以最后要加入0

思路:

  • 枚举所有可能的位运算结果,利用set去重后统计数量

T3:最小分组

错误原因:没有考虑 a[i] > mid 的情况

思路:

  • 二分答案:猜测一个 “最大和”mid,判断是否能将数组划分为 ≤k 个子数组,且每个子数组的和都不超过mid
    贪心验证:模拟 “划分过程”,计算需要多少个子数组才能让所有子数组的和 ≤x,若所需子数组数量 ≤k,则 x 可行
bool check(LL x) {
    LL sum = 0, cnt = 1;  // sum:当前子数组的和;cnt:已用子数组数量(初始为1,至少有一个子数组)
    for (int i = 1; i <= n; ++i) {
        if (a[i] > x) return false;  // 单个元素就超过x,不可能划分
        if (sum + a[i] <= x) {
            sum += a[i];  // 能加入当前子数组,累加和
        } else {
            sum = a[i];   // 不能加入,新起一个子数组
            cnt++;        // 子数组数量+1
        }
    }
    return cnt <= k;  // 所需子数组数量≤k时,x可行
}

Day 5

分数:0+100+100+50

T1:最小值之和

错误原因:十年OI一场空,不开longlong见祖宗

思路:纯暴力!

for (int i = 1; i <= n; i++) {
		cin >> b[i];
		cnt += min (a[i], b[i]);
	}
	while (m--) {
		char ch;
		int x, v;
		cin >> ch >> x >> v;
		if (ch == 'A') {
			cnt -= min (a[x], b[x]);
			
			cnt += min (v, b[x]);
			a[x] = v;
		} else {
			cnt -= min (a[x], b[x]);
			cnt += min (v, a[x]);
			b[x] = v;
		}
		cout << cnt << '\n';
	}
}

T4:越野大王

错误原因:BFS学得不太好,以为搜索过不了,写了个dp…

思路:

  • 正常 BFS 即可,多加一层循环遍历步数,如果走不了了就break
while (!q.empty()) {
		Node now = q.front(); q.pop();
		for (int i = 0; i < 4; i++) {
			int now_cnt = now.cnt;
			for (int l = 1; l <= k; l++) {
				int now_x = now.x + dx[i] * l;
				int now_y = now.y + dy[i] * l;
				if (now_x < 1 || now_y < 1 || now_x > n || now_y > m || c[now_x][now_y] == '#') {
					break;
				}
				if (vis[now_x][now_y] == 1) {
					continue;
				}
				if (now_x == ex && now_y == ey) {
					cout << now_cnt + 1;
					return ;
				}
				vis[now_x][now_y] = 1;
				q.push({now_x, now_y, now_cnt + 1});
			}
		}
	}
}

Day 6

分数:100+100+30+40

T3:疯狂的异或大王

错误原因:题目的锅,样例解释去重了,测试点没去重,都是题目的错!

思路:

  • 前缀异或
  • c % 2 == 1时,ans ^= sum[r] ^ sum[l - 1]
  • c % 2 == 0时,ans ^= 0

T4:小信的迷宫

错误原因:想太复杂了,我甚至去记录离该点最近的开关格子去走回头路。。。还有就是考试时小写的o和x写成大写的了

思路:

  • 正常BFS即可,记录一下走过开关格子的奇偶性,判断是否能走ox

Day 7

分数:0+100+100+20

T1:骑行旅游

错误原因:一开始忘开long long了,提交时把long long补上了,但min函数中的一个常数没开long long,CE了

T4:清羽的小熊猫

错误原因:二阶差分谁想得到啊!

思路:

  • “最小天数” 具有单调性,所以考虑 二分
  • 每天的投喂区间 [l_i, r_i] 内,投喂量是线性递增的(位置 pos 的投喂量为 pos - l_i + 1 )。这种 “线性递增的区间和” 可通过两次差分数组 + 两次前缀和快速计算
  • 差分数组的构造逻辑(以第 i 天的区间 [l, r] 为例):
    第一次差分标记:
    d[l] += 1:区间起点的 “增量标记”。
    d[r + 1] -= (r - l + 1 + 1):区间终点后一位的 “减量标记”。
    d[r + 2] += (r - l + 1):区间终点后两位的 “补偿标记”。
    两次前缀和:
    第一次前缀和:d[i] += d[i-1](初步累加差分标记)。
    第二次前缀和:d[i] += d[i-1](最终得到每个位置的累计投喂量)。
  • check函数
bool check(int x) {
    memset(d, 0, sizeof d);  // 每次验证前清空差分数组
    for (int i = 1; i <= x; ++i) {  // 处理前x天的投喂
        int l = L[i], r = R[i];
        ++d[l];  // 区间起点标记
        d[r+1] -= r - l + 1 + 1;  // 区间终点后一位标记
        d[r+2] += r - l + 1;      // 区间终点后两位补偿
    }
    // 第一次前缀和
    for (int i = 1; i <= L; ++i) {
        d[i] += d[i-1];
    }
    // 第二次前缀和(得到每个位置的累计投喂量)
    for (int i = 1; i <= L; ++i) {
        d[i] += d[i-1];
    }
    int cnt = 0;  // 统计≥K的位置数
    for (int i = 1; i <= L; ++i) {
        cnt += (d[i] >= K);
    }
    return cnt >= Q;  // 判断是否满足目标
}
  • 二分
void solve() {
    cin >> L >> n >> K >> Q;
    for (int i = 1; i <= n; ++i) {
        cin >> L[i] >> R[i];  // 读取每天的投喂区间
    }
    int Left = 0, Right = n + 1;  // 二分边界:最少0天,最多n+1天(超过则无解)
    while (Left <= Right) {
        int mid = (Left + Right) >> 1;  // 取中间天数验证
        if (check(mid)) {  // 前mid天满足条件,尝试更小的天数
            Right = mid - 1;
        } else {  // 不满足,尝试更大的天数
            Left = mid + 1;
        }
    }
    // 最终Left是“最小满足条件的天数”,若超过n则无解
    if (Left <= n) cout << Left << '\n';
    else cout << "-1\n";
}
6 个赞

day7 T4也是很有生活了,重测完数据发现我竟然从第三到了第一

1 个赞

考试时小写的o和x写成大写的了 :face_with_rolling_eyes:

1 个赞