2026-01-12~01-13 hetao1733837 的刷题笔记
01-12
LGP3287 [SCOI2014] 方伯伯的玉米田
原题链接:[SCOI2014] 方伯伯的玉米田
分析
紫题是 hyw ?
被家长和数学老师压力文化课了😅
确实,一天之中,要学习两门竞赛课,而且底层还不太一样,确实消耗很大,还有六科文化等着我......
突然意识到了 OI 的底层:Trick积累(有效刷题)+场上盯出来(思维品质+注意力)+调试代码(代码能力) 。
那么,按照这个前行吧!
开始写文化课吧,天天被压力/ll
所以,昨天晚上我并没有读题,而且,回寝熄灯之后去隔壁寝室听化学,被宿管抓了/ll
所以,我还是先写个反思吧......
要不说DeepSeek🐂🍺呢?写完了
那,看题吧!
看到 tag 里的树状数组,我觉得此题和逆序对有点关系......
手模样例来一发......
这个样例是人就会啊......毫无启发性可言。
似乎有两个转移思路,
其一是设 d p i , j dp_{i,j} dpi,j 表示必须选 i i i,序列长度为 j j j 的最小方案数。我不会转移。
其二是设 d p i , k dp_{i,k} dpi,k 表示考虑了前 i i i 个位置,有 k k k 次操作的最长序列长度。
但是,删除怎么办?这个也状压不进去啊?难道从记忆化搜索入手?
其三是设 d p i , j , k dp_{i,j,k} dpi,j,k 表示考虑前 i i i 个位置,删除了 j j j 个,拔高了 k k k 个的最长序列长度。
但我都不会转移。先去把反思交了。
交了,还和 ssy 选手交流了一下。
我要看题解!
正确思路
从一个贪心出发,就是,我每次增长的区间的右端点必须是整个原始序列的最右端。
乍一看很反直觉,但是,如果不是最右端,反而可能破坏原来已经出现的不下降子序列,而最右端是随便增长,绝对不劣。
那么,设出状态 f i , j f_{i,j} fi,j 表示考虑前 i i i 个位置, i i i 位置已经被 j j j 个拔高区间覆盖过,所得到的最长不下降子序列的长度。
则有转移方程 f i , j = max k = 1 , l = 0 k ≤ i , l ≤ j , h i + j ≥ h k + l { f k , l } + 1 f_{i,j}=\max\limits_{k=1,l=0}^{k\le i,l\le j,h_i+j\ge h_k+l}\{f_{k,l}\}+1 fi,j=k=1,l=0maxk≤i,l≤j,hi+j≥hk+l{fk,l}+1,时间上显然很劣,我们需要一个 DS 快速求取前缀 max \max max,想到了树状数组(虽然感觉线段树更好一点吧......),拿一个二维树状数组维护。
考虑继续优化, i i i 这一维似乎意义不大,但是 h i + j ≥ h k + l h_i+j\ge h_k+l hi+j≥hk+l 又需要辅助判断,所以,设 f j , k f_{j,k} fj,k 表示当前某个点 i i i,以一个不超过 j = h i + k j = h_i+k j=hi+k 的高度,被不超过 k k k 个拔高区间覆盖后以该节点为最长不下降子序列的长度。
哦,好像有点理解了,我要回去看看《算法竞赛》什么说法。
哦,其实大差不差,我都不理解......
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, K = 505, A = 5505;
int a[N], dp[N][K], c[K][A], n, k;
void add(int x, int y, int val){
for (int i = x; i <= k + 1; i += i & (-i)){
for (int j = y; j <= 5500; j += j & (-j)){
c[i][j] = max(c[i][j], val);
}
}
}
int query(int x, int y){
int res = 0;
for (int i = x; i; i -= i & (-i)){
for (int j = y; j; j -= j & (-j)){
res = max(res, c[i][j]);
}
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++){
cin >> a[i];
}
for (int i = 1; i <= n; i++){
for (int j = k; j >= 0; j--){
dp[i][j] = query(j + 1, a[i] + j) + 1;
add(j + 1, a[i] + j, dp[i][j]);
}
}
cout << query(k + 1, 5500);
}
01-13
LGP2605 [ZJOI2010] 基站选址
原题链接:[ZJOI2010] 基站选址
分析
感觉会但是写不出来咋办?那就看书📕......
设 d p i , j dp_{i,j} dpi,j 表示前 i i i 个村庄内第 j j j 个基站建在 i i i 处的最小费用。
则有转移 d p i , j = min k = j − 1 k < i { d p k , j − 1 + p a y k , i } dp_{i,j}=\min\limits_{k=j-1}^{k<i}\{dp_{k,j-1}+pay_{k,i}\} dpi,j=k=j−1mink<i{dpk,j−1+payk,i},其中 p a y k , i pay_{k,i} payk,i 表示区间 [ k , i ] [k,i] [k,i] 内没有基站 i , k i,k i,k 覆盖村庄的赔偿。
发现转移只从上一个而来,直接滚掉,然后,变成一个区间 min \min min,掏出线段树。
然后,我似乎看到了些许离散化的影子,因为 D i D_i Di 太大了。
正解
cpp
#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 20005;
int n, k, d[N], c[N], s[N], w[N];
int st[N], ed[N], dp[N];
struct node{
int to;
node *nxt;
}a[N << 2], *lst[N], *e = a;
void add(int x, int y){
e -> nxt = lst[x];
e -> to = y;
lst[x] = e++;
}
struct segtree{
int mn[N << 2], tag[N << 2];
void pushup(int p){
mn[p] = min(mn[p << 1], mn[p << 1 | 1]);
}
void maketag(int p, int val){
mn[p] += val;
tag[p] += val;
}
void down(int p){
if (!tag[p])
return ;
maketag(p << 1, tag[p]);
maketag(p << 1 | 1, tag[p]);
tag[p] = 0;
}
void build(int p, int l, int r){
tag[p] = 0;
if (l == r){
mn[p] = dp[l];
return ;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p);
}
void modify(int p, int l, int r, int s, int t, int val){
if (s > t) return;
if (l == s && r == t){
maketag(p, val);
return ;
}
down(p);
int mid = (l + r) >> 1;
if (t <= mid)
modify(p << 1, l, mid, s, t, val);
else if (s > mid)
modify(p << 1 | 1, mid + 1, r, s, t, val);
else{
modify(p << 1, l, mid, s, mid, val);
modify(p << 1 | 1, mid + 1, r, mid + 1, t, val);
}
pushup(p);
}
int query(int p, int l, int r, int s, int t){
if (s > t) return inf;
if (l == s && r == t){
return mn[p];
}
down(p);
int mid = (l + r) >> 1;
if (t <= mid)
return query(p << 1, l, mid, s, t);
else if (s > mid)
return query(p << 1 | 1, mid + 1, r, s, t);
else
return min(query(p << 1, l, mid, s, mid), query(p << 1 | 1, mid + 1, r, mid + 1, t));
}
}T;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 2; i <= n; i++){
cin >> d[i];
}
for (int i = 1; i <= n; i++){
cin >> c[i];
}
for (int i = 1; i <= n; i++){
cin >> s[i];
}
for (int i = 1; i <= n; i++){
cin >> w[i];
}
n++;
k++;
d[n] = w[n] = inf;
for (int i = 1; i <= n; i++){
st[i] = lower_bound(d + 1, d + n + 1, d[i] - s[i]) - d;
ed[i] = lower_bound(d + 1, d + n + 1, d[i] + s[i]) - d;
if (d[ed[i]] > d[i] + s[i])
ed[i]--;
add(ed[i], i);
}
int now = 0;
for (int j = 1; j <= n; j++){
dp[j] = now + c[j];
for (node *p = lst[j]; p; p = p->nxt){
int v = p->to;
now += w[v];
}
}
int ans = dp[n];
for (int i = 2; i <= k; i++){
T.build(1, 1, n);
for (int j = 1; j <= n; j++){
dp[j] = T.query(1, 1, n, 1, j - 1) + c[j];
for (node *p = lst[j]; p; p = p->nxt){
int v = p->to;
if (st[v] > 1)
T.modify(1, 1, n, 1, st[v] - 1, w[v]);
}
}
ans = min(ans, dp[n]);
}
cout << ans;
}
感觉并非很好吧......没有了前几天的状态,这样继续 he 下赛季肯定还是这样的/ll
LGP2627 [USACO11OPEN] Mowing the Lawn G
原题链接:[USACO11OPEN] Mowing the Lawn G
分析
行,看懂题了......
那么,我设 d p i dp_{i} dpi 表示考虑了前 i i i 个位置, i i i 位置强制不选,那么,转移就从他前面 k 个找个最大值,套一个前缀和加个速,应该......可能......我会吧......吗?
那我的答案咋更新?我答案咋表示?难道我建一个虚点 n+1 ?这是个挺牛的思路的。
但是,没必要那么麻烦,设 d p i dp_{i} dpi 表示考虑了前 i i i 个位置的最大答案就可以了,因为在转移过程中直接有断裂的操作了。同样的转移。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100005;
int n, k, dp[N], e[N];
int q[N], head = 0, tail = 1;
int sum[N], c[N];
int query_mx(int p){
c[p] = dp[p - 1] - sum[p];
while (head <= tail && c[q[tail]] < c[p]){
tail--;
}
q[++tail] = p;
while (head <= tail && q[head] < p - k){
head++;
}
return c[q[head]];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++){
cin >> e[i];
sum[i] = sum[i - 1] + e[i];
}
for (int i = 1; i <= n; i++){
dp[i] = query_mx(i) + sum[i];
}
cout << dp[n];
}
LGP3089 [USACO13NOV] Pogo-Cow S
分析
通过一些贪心的视角,我认为,从两端作为起点是不太劣的,因为 p i p_i pi 都是非负数。这个是错的啊!好像是后面选择的一些小问题吧,反正细想一下是错的。
然后呢? 我......太惭愧了......这么典的题竟然不会!记到本上!开始看题解!这个必须认真!
设 d p i , j dp_{i,j} dpi,j 表示当前已经跳到了第 i i i 个位置,上一个位置是 j j j 的最大值,转移显然(以向右条为例),
d p i , j = max k = x 1 k < j , x j − x k ≤ x i − x j { d p j , k } + p i dp_{i,j}=\max\limits_{k=x_1}^{k<j,x_j-x_k\le x_i-x_j}\{dp_{j,k}\}+p_i dpi,j=k=x1maxk<j,xj−xk≤xi−xj{dpj,k}+pi
然后,我没有一个严格的窗口长度,所以,还是不太能优化,所以,再观察一步,我们发现,
d p i − 1 , j = max k k < j , x j − x k ≤ x i − 1 − x j { d p j , k } + p i − 1 dp_{i-1,j}=\max\limits_{k}^{k<j,x_j-x_k\le x_{i-1}-x_j}\{dp_{j,k}\}+p_{i-1} dpi−1,j=kmaxk<j,xj−xk≤xi−1−xj{dpj,k}+pi−1
那么, d p i , j = d p i − 1 , j − p i − 1 + p i dp_{i,j}=dp_{i-1,j}-p_{i-1}+p_i dpi,j=dpi−1,j−pi−1+pi,
但是, d p i − 1 , j dp_{i-1,j} dpi−1,j 定义在 x j − x k ≤ x i − 1 − x j x_j-x_k\le x_{i-1}-x_j xj−xk≤xi−1−xj 上, x i − 1 ≤ x i x_{i-1}\le x_i xi−1≤xi,所以,这里的 k k k 会更多,所以,确定一个最大转移的 k k k 即可。
做完了。
会被 Hack ,我不知道为什么,但是,我特判把 Hack 过了......哦,所以 Hack \operatorname{Hack} Hack 是卡你那个 DP 成立条件的,直接特判问题并不大。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1005;
int n;
struct node{
int x, p;
}a[N];
int dp[N][N];
bool cmp(node tmp1, node tmp2){
return tmp1.x < tmp2.x;
}
int ans;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> a[i].x >> a[i].p;
}
if (n == 1){
cout << a[1].p;
return 0;
}
sort(a + 1, a + n + 1, cmp);
for (int j = 1; j <= n; j++){
dp[j][j] = a[j].p;
int k = j + 1;
for (int i = j + 1; i <= n; i++){
dp[i][j] = dp[i - 1][j] - a[i - 1].p;
while (k > 1 && a[j].x - a[k - 1].x <= a[i].x - a[j].x)
dp[i][j] = max(dp[i][j], dp[j][--k]);
dp[i][j] += a[i].p;
ans = max(ans, dp[i][j]);
}
}
for (int j = n; j >= 1; j--){
dp[j][j] = a[j].p;
int k = j - 1;
for (int i = j - 1; i >= 1; i--){
dp[i][j] = dp[i + 1][j] - a[i + 1].p;
while (k < n && a[k + 1].x - a[j].x <= a[j].x - a[i].x)
dp[i][j] = max(dp[i][j], dp[j][++k]);
dp[i][j] += a[i].p;
ans = max(ans, dp[i][j]);
}
}
cout << ans;
}
行,我要写文化课作业了,今天应该是过了......我数数啊......三道半......还行吧,保证质量,继续努力✊