2026-01-12~01-13 hetao1733837 的刷题笔记

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

原题链接:[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;
}

行,我要写文化课作业了,今天应该是过了......我数数啊......三道半......还行吧,保证质量,继续努力✊

相关推荐
一起养小猫2 小时前
LeetCode100天Day13-移除元素与多数元素
java·算法·leetcode
a***59263 小时前
C++跨平台开发:挑战与解决方案
开发语言·c++
ACERT3333 小时前
10.吴恩达机器学习——无监督学习01聚类与异常检测算法
python·算法·机器学习
诗词在线3 小时前
从算法重构到场景复用:古诗词数字化的技术破局与落地实践
python·算法·重构
不穿格子的程序员3 小时前
从零开始写算法——二叉树篇7:从前序与中序遍历序列构造二叉树 + 二叉树的最近公共祖先
数据结构·算法
Yu_Lijing3 小时前
基于C++的《Head First设计模式》笔记——外观模式
c++·笔记·设计模式
CoderCodingNo3 小时前
【GESP】C++六级考试大纲知识点梳理, (5) 动态规划与背包问题
开发语言·c++·动态规划
情缘晓梦.3 小时前
C++ 类和对象(完)
开发语言·jvm·c++
代码游侠3 小时前
学习笔笔记——ARM 嵌入式系统与内核架构
arm开发·笔记·嵌入式硬件·学习·架构