Dec. 20th 2025
Dec. 20th 2025
周测
爆零了
T1 (Minimal Segment Cover)
E. Minimal Segment Cover
time limit per test: 2 seconds
memory limit per test: 256 megabytes
input: standard input
output: standard output
You are given \(n\) intervals in form \([l; r]\) on a number line.
You are also given \(m\) queries in form \([x; y]\). What is the minimal number of intervals you have to take so that every point (not necessarily integer) from \(x\) to \(y\) is covered by at least one of them?
If you can't choose intervals so that every point from \(x\) to \(y\) is covered, then print -1 for that query.
Input
The first line contains two integers \(n\) and \(m\) (\(1 \le n, m \le 2 \cdot 10^5\)) — the number of intervals and the number of queries, respectively.
Each of the next \(n\) lines contains two integer numbers \(l_i\) and \(r_i\) ($0\le l_i < r_i \le 5 \cdot 10^5 $) — the given intervals.
Each of the next \(m\) lines contains two integer numbers \(x_i\) and \(y_i\) ($0\le x_i <y_i\le 5 \cdot 10^5 $) — the queries.
Output
Print \(m\) integer numbers. The \(i\)-th number should be the answer to the \(i\)-th query: either the minimal number of intervals you have to take so that every point (not necessarily integer) from \(x_i\) to \(y_i\) is covered by at least one of them or -1 if you can't choose intervals so that every point from \(x_i\) to \(y_i\) is covered.
Output
Print \(m\) integer numbers. The \(i\)-th number should be the answer to the \(i\)-th query: either the minimal number of intervals you have to take so that every point (not necessarily integer) from \(x_i\) to \(y_i\) is covered by at least one of them or -1 if you can't choose intervals so that every point from \(x_i\) to \(y_i\) is covered.
Examples
input
2 3
1 3
2 4
1 3
1 4
3 4
output
1
2
1
input
3 4
1 3
1 3
4 5
1 2
1 3
1 4
1 5
output
1
1
-1
-1
原题,但是没A
#include<bits/stdc++.h>
using namespace std;
using lf = double;
using ll = long long;
using ull = unsigned long long;
const int maxn = 2e5 + 5;
const int maxN = 5e5 + 5;
int dp[25][maxN];
int n, m;
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int l, r;
cin >> l >> r;
dp[0][l] = max(dp[0][l], r);
}
for (int i = 1; i < maxN - 4; i++)
dp[0][i] = max(dp[0][i], dp[0][i - 1]);
for (int i = 1; i <= 18; i++)
for (int j = 0; j < maxN - 4; j++)
dp[i][j] = dp[i - 1][dp[i - 1][j]];
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
int ans = 0;
for (int k = 18; ~k ; k--) {
if (dp[k][x] < y) {
ans |= 1 << k;
x = dp[k][x];
}
}
cout << (ans < n ? ans + 1 : -1) << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// freopen("T1.in", "r", stdin);
// freopen("T1.out", "w", stdout);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}
T2(Colourful Rectangle)
有一篇好的题解,比较抽象:hdu 4419 线段树 扫描线 离散化 矩形面积
还有一篇:HDU 4419 Colourful Rectangle --离散化+线段树扫描线
用的第二篇题解的思路
\(\textcolor{red}{\text{注意}}\) : 按照此方法,3 和 4 是反着的,需要矫正
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
using lf = double;
using ll = long long;
using ull = unsigned long long;
const int maxn = 1e4 + 107;
const int maxnode = maxn * 8;
struct scanline {
int y;
int left_x, right_x;
int col;
scanline() = default;
scanline(int y_, int l, int r, int c) : y(y_), left_x(l), right_x(r), col(c) {}
} tree[maxn << 1];
ll length[8][maxnode];
int cover[4][maxnode];
int x[maxn * 2];
int cnt, num;
int to = 0;
int ls(int id) {return id << 1;}
int rs(int id) {return id << 1 | 1;}
bool cmp(const scanline &a, const scanline &b) {return a.y < b.y;}
void maitain(int id, int left, int right) {
int state(0);
for (int i = 1; i <= 3; i++) if (cover[i][id]) state |= (1 << (i - 1));
for (int i = 0; i < 8; i++) length[i][id] = 0;
if (left + 1 == right) {
length[state][id] = x[right] - x[left];
return ;
}
for (int i = 0; i < 8; i++) length[i | state][id] += length[i][ls(id)] + length[i][rs(id)];
}
void init(int id, int left, int right) {
for (int i = 1; i <= 3; i++) cover[i][id] = 0;
for (int i = 0; i < 8; i++) length[i][id] = 0;
length[0][id] = x[right] - x[left];
if (left + 1 == right) return;
int mid = (left + right) >> 1;
init(ls(id), left, mid);
init(rs(id), mid, right);
}
void update(int L, int R, int cov, int id = 1, int left = 1, int right = num) {
if (L <= left and right <= R) {
if (cov > 0)
cover[cov][id]++;
else
cover[-cov][id]--;
maitain(id, left, right);
return ;
}
if (left + 1 == right) return;
int mid = (left + right) >> 1;
if (L < mid) update(L, R, cov, ls(id), left, mid);
if (R > mid) update(L, R, cov, rs(id), mid, right);
maitain(id, left, right);
}
void solve() {
int n, c;
cin >> n;
cnt = 0;
memset(x, 0, sizeof(x));
for (int i = 1; i <= n; i++) {
int x1, x2, y_1, y_2;
char col;
cin >> col >> x1 >> y_1 >> x2 >> y_2;
if (col == 'R') c = 1;
else if (col == 'G') c = 2;
else c = 3;
tree[++cnt] = scanline(y_1, x1, x2, c);
x[cnt] = x1;
tree[++cnt] = scanline(y_2, x1, x2, -c);
x[cnt] = x2;
}
sort(x + 1, x + cnt + 1);
num = unique(x + 1, x + cnt + 1) - (x + 1); // 离散化
init(1, 1, num);
sort(tree + 1, tree + cnt + 1, cmp);
ll ans[8] = {0};
cout << "Case " << ++to << ":\n";
for (int i = 1; i < cnt; i++) {
int L = lower_bound(x + 1, x + num + 1, tree[i].left_x) - x;
int R = lower_bound(x + 1, x + num + 1, tree[i].right_x) - x;
update(L, R, tree[i].col, 1, 1, num);
for (int j = 1; j < 8; j++) ans[j] += length[j][1] * (tree[i + 1].y - tree[i].y);
}
swap(ans[3], ans[4]);
for (int i = 1; i < 8; i++)
cout << ans[i] << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--)
solve();
return 0;
}
T3(CF833B The Bakery)
CF833B The Bakery
题目描述
不久前,Slastyona the Sweetmaid 决定开设自己的面包店!她买齐了所需的原料,还有一台可以烘焙多种蛋糕的奇妙烤箱,并开张了面包店。
很快,开支开始超过收入,于是 Slastyona 决定研究甜点市场。她了解到,将蛋糕装入盒子会更有利可图,而且每个盒子里不同种类蛋糕越多(我们将这个数量称为盒子的价值),价格就越高。
她需要改变生产技术!问题在于,烤箱会自行决定蛋糕的种类,Slastyona 无法干预。然而,她知道今天烤箱将要依次烘烤出 \(n\) 个蛋糕的类型和顺序。今天,Slastyona 必须准确地用 \(k\) 个盒子来打包这些蛋糕,并且每个盒子内必须放入若干(至少一个)连续烘焙出来的蛋糕(换句话说,每个盒子包含一段蛋糕的连续区间)。
Slastyona 希望最大化所有盒子的总价值。请你帮她求出所有盒子的最大可能总价值。
输入格式
第一行包含两个整数 \(n\) 和 \(k\)(\(1 \leq n \leq 35000\),\(1 \leq k \leq \min(n, 50)\))——蛋糕的数量以及盒子的数量。
第二行包含 \(n\) 个整数 \(a_{1}, a_{2}, ..., a_{n}\)(\(1 \leq a_{i} \leq n\))——按照烤箱生产顺序排列的蛋糕类型。
输出格式
输出唯一一个整数,表示所有盒子中的蛋糕最大可能的总价值。
输入输出样例 #1
输入 #1
4 1
1 2 2 1
输出 #1
2
输入输出样例 #2
输入 #2
7 2
1 3 3 1 4 4 4
输出 #2
5
输入输出样例 #3
输入 #3
8 3
7 7 8 7 7 8 1 7
输出 #3
6
说明/提示
在第一个样例中,Slastyona 只有一个盒子。她必须把所有蛋糕都放进这个盒子里,因此这个盒子有两种类型,价值为 \(2\)。
在第二个样例中,最优策略是将前两个蛋糕放入第一个盒子,其余的都放进第二个盒子。则第一个盒子有两种类型,第二个盒子有三种类型,因此总价值为 \(5\)。
由 ChatGPT 5 翻译
教练题解:
View Solution
题目大意
给定一个长度为 \(n\)(\(n \le 35000\))的序列 \(a\),要求将其分成 \(k\)(\(k \le \min(n, 50)\))段连续的子序列。每段的价值定义为该段中不同数字的个数。目标是最大化所有段的价值之和。
解题思路
2.1 基础动态规划
设 \(dp[i][j]\) 表示前 \(i\) 个蛋糕分成 \(j\) 段的最大总价值。转移方程为:
其中 \(\text{val}(l, r)\) 表示区间 \([l, r]\) 中不同数字的个数。
直接计算 \(\text{val}(l, r)\) 需要 \(O(n)\) 时间,总复杂度为 \(O(n^2 k)\),无法通过本题。
2.2 线段树优化
观察转移方程,对于固定的 \(j\),我们需要维护所有 \(x\) 对应的 \(dp[x][j-1] + \text{val}(x+1, i)\) 的最大值。关键点在于 \(\text{val}(x+1, i)\) 会随着 \(i\) 的增加而变化。
核心观察:当 \(i\) 从 \(i-1\) 增加到 \(i\) 时,只有那些包含 \(a[i]\) 且 \(a[i]\) 是第一次出现的区间,\(\text{val}\) 值才会增加 \(1\)。
具体来说,设 \(pre[i]\) 表示 \(a[i]\) 上一次出现的位置(若未出现过则为 \(0\))。那么对于所有 \(x \in [pre[i]+1, i]\),区间 \([x+1, i]\) 都包含 \(a[i]\),且 \(a[i]\) 在这些区间中是第一次出现(因为 \(x \ge pre[i]+1\),所以 \(x+1 > pre[i]\),即 \(a[i]\) 上一次出现的位置不在区间内)。
因此,我们可以用线段树维护每个 \(x\) 对应的 \(dp[x][j-1] + \text{val}(x+1, i)\) 值:
初始化:将叶子节点 \(x\) 的值设为 \(dp[x][j-1]\)
当处理到 \(i\) 时:将区间 \([pre[i]+1, i]\) 的值加 \(1\)
查询:\(dp[i][j]\) 即为线段树中区间 \([j-1, i-1]\) 的最大值
这样,每次更新和查询都是 \(O(\log n)\),总复杂度为 \(O(nk \log n)\),可以通过本题。
标准程序
// 被吃了...
// eaten by dog
时间复杂度
预处理 \(pre\) 数组:\(O(n)\)
DP过程:外层循环 \(k\) 次,内层循环 \(n\) 次,每次线段树操作 \(O(\log n)\)
总时间复杂度:\(O(nk \log n)\)
对于本题数据范围 \(n \le 35000\),\(k \le 50\),该算法可以在时限内通过。
动态规划优化,线段树,一维动态规划,多维动态规划
My Code:
#include<bits/stdc++.h>
using namespace std;
using lf = double;
using ll = long long;
using ull = unsigned long long;
const int maxn = 3.5e4 + 5;
ll ls(ll i) {
return i << 1;
}
ll rs(ll i) {
return i << 1 | 1;
}
ll a[maxn];
ll dp[maxn][55];
ll tree[maxn << 2], lazy_tag[maxn << 2];
int n, k;
int pre[maxn], last[maxn];
void maintain(ll id) { // push up
tree[id] = max(tree[ls(id)], tree[rs(id)]);
}
void build(int id, int left_, int right_, int posi) {
lazy_tag[id] = 0;
if (left_ == right_) {
tree[id] = dp[left_][posi];
return;
}
ll mid = (left_ + right_) >> 1;
build(ls(id), left_, mid, posi);
build(rs(id), mid + 1, right_, posi);
maintain(id);
}
void addtag(int id, int left_, int right_, int d) {
lazy_tag[id] += d;
tree[id] += d;
}
void pushdown(int id, int left_, int right_) {
if (lazy_tag[id]) {
ll mid = (left_ + right_) >> 1;
addtag(ls(id), left_, mid, lazy_tag[id]);
addtag(rs(id), mid + 1, right_, lazy_tag[id]);
lazy_tag[id] = 0;
}
}
void update(ll L, ll R, ll d, int id = 1, int left_ = 0, int right_ = n) {
if (L <= left_ and right_ <= R) {
addtag(id, left_, right_, d);
return;
}
pushdown(id, left_, right_);
ll mid = (left_ + right_) >> 1;
if (L <= mid) update(L, R, d, ls(id), left_, mid);
if (R > mid) update(L, R, d, rs(id), mid + 1, right_);
maintain(id);
}
ll query(ll L, ll R, int id = 1, int left_ = 0, int right_ = n) {
if (L <= left_ and right_ <= R)
return tree[id];
pushdown(id, left_, right_);
ll mid = (left_ + right_) >> 1;
ll res = 0;
if (L <= mid) res = max(res, query(L, R, ls(id), left_, mid));
if (R > mid) res = max(res, query(L, R, rs(id), mid + 1, right_));
return res;
}
void solve() {
cin >> n >> k;
memset(dp, -0x3f, sizeof dp);
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pre[i] = last[a[i]];
last[a[i]] = i;
dp[i][0] = 0;
}
for (int i = 1; i <= k; i++) {
build(1, 0, n, i - 1);
for (int j = i; j <= n; j++) {
update(pre[j], j - 1, 1);
dp[j][i] = query(i - 1, j - 1);
}
}
cout << dp[n][k] << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
while (T--) solve();
return 0;
}

浙公网安备 33010602011771号