2026-01-16~19 hetao1733837 的刷题笔记

2026-01-16~17 hetao1733837 的刷题笔记

01-16

哦,祝姐姐生日快乐🎂!

AT_abc248_h [ABC248Ex] Beautiful Subsequences

链接:aoao20 这真的是题

分析

这个主要是那个分治的写法。这个存疑吧......我并非非常理解这个东西( k ≤ 3 k\le3 k≤3我也没理解完全)

那我打一点分治题吧......

LGP4544 [USACO10NOV] Buying Feed G

原题链接:[USACO10NOV] Buying Feed G

分析

有点多重背包的感觉了,这里似乎还要套一个小小的二分,因为那个 y = x 2 y=x^2 y=x2 在 ( 0 , + ∞ ) (0,+\infty) (0,+∞) 上是单调递增的。

好吧,我没盯出来状态(偷偷看一眼)

显然状态盯对了,只是转移没整出来,我们设 d p i , j dp_{i,j} dpi,j 表示到达了第 i i i 家店,已经有 j j j 吨货物的最小花费。

转移有: d p i , j = min ⁡ ( d p i , j , d p i − 1 , k + ( j − k ) × C i + ( X i − X i − 1 ) × k 2 ) dp_{i,j}=\min(dp_{i,j}, dp_{i-1,k}+(j-k)\times C_i+(X_i-X_{i-1})\times k^2) dpi,j=min(dpi,j,dpi−1,k+(j−k)×Ci+(Xi−Xi−1)×k2)。

因该长这样吧......题解没用 Latex 看得挺抽象的。

拆一下,得到 d p i , j = min ⁡ ( d p i , j , d p i − 1 , k − k × C i + ( X i − X i − 1 ) × k 2 ) + j × C i dp_{i,j}=\min(dp_{i,j}, dp_{i-1,k}-k\times C_i+(X_i-X_{i-1})\times k^2) + j \times C_i dpi,j=min(dpi,j,dpi−1,k−k×Ci+(Xi−Xi−1)×k2)+j×Ci

拆完感觉有点反直觉。算了,继续拆。
d p i , j = min ⁡ k = j − F i j { d p i − 1 , k − k × C i + ( X i − X i − 1 ) × k 2 } + j × C i dp_{i,j}=\min\limits_{k=j-F_i}^{j}\{dp_{i-1,k}-k\times C_i+(X_i-X_{i-1})\times k^2\} + j \times C_i dpi,j=k=j−Fiminj{dpi−1,k−k×Ci+(Xi−Xi−1)×k2}+j×Ci

那,似乎成了一个长度为 F i F_i Fi 的滑动窗口......还是很反直觉啊......但是,我们有一个 K ≤ 10000 K\le 10000 K≤10000, N ≤ 300 N\le300 N≤300,似乎比较能接受吧......

似乎没有一篇题解能看吧......

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505, K = 10005;
int k, e, n;
struct node{
	int x, f, c;
}inp[N];
int dp[N][K], q[K], head, tail;
int calc(int i, int j){
	return dp[i - 1][j] + j * j * (inp[i].x - inp[i - 1].x) - inp[i].c * j;
}
bool cmp(node a, node b){
	return a.x < b.x;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> k >> e >> n;
	for (int i = 1; i <= n; i++){
		cin >> inp[i].x >> inp[i].f >> inp[i].c;
	}
	sort(inp + 1, inp + n + 1, cmp);
	memset(dp, 0x3f, sizeof(dp));
	dp[0][0] = 0;
	inp[0].x = 0;
	for (int i = 1; i <= n; i++){
		head = 1;
		tail = 0;
		for (int j = 0; j <= k; j++){
			while (head <= tail && calc(i, q[tail]) > calc(i, j))
				tail--;
			while (head <= tail && q[head] < j - inp[i].f)
				head++;
			q[++tail] = j;
			dp[i][j] = calc(i, q[head]) + inp[i].c * j;
		}
	}
	cout << dp[n][k] + k * k * (e - inp[n].x);
	return 0;
}

01-17

AtCoder-abc355_f [ABC355F] MST Query

原题链接1:[ABC355F] MST Query

原题链接2:F - MST Query

分析

呃......课上整的有点抽象了。要不我还是下一个客户端吧。

机房电脑带不动客户端......

我还是直接看题解吧......

一开始以为是离线......这个确实很典,但是,这题有一个很关键的 w i ≤ 10 w_i\le 10 wi≤10,由于最小生成树的偏序最优性(其实就很贪吧......),然后我们关心的是,边权不超过 w i w_i wi 的边将点集划分成了多少连通分量......哦?这个是个好题啊!在我最开始学最小生成树的时候,我的老师告诉我一个性质:在最小生成树中,对于任意一条非树边,都大于其两端点在 MST 上的距离

好像是这么说的吧......哦......那很好办了啊!我们维护 10 个并查集做完了......

然后,在那个实现过程中,把最小生成树的权值初始化为 10 × ( n − 1 ) 10\times(n-1) 10×(n−1),然后只需要枚举到 9 9 9,即可。

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200005, M = 15;
int n, q, fa[N][M];
int find(int x, int id){
	return x == fa[x][id] ? x : fa[x][id] = find(fa[x][id], id);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> q;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j < 10; j++){
			fa[i][j] = i;
		}
	}
	int ans = 10 * (n - 1);
	for (int i = 1, u, v, w; i < n + q; i++){
		cin >> u >> v >> w;
		for (int j = w; j < 10; j++){
			int fu = find(u, j);
			int fv = find(v, j);
			if (fu != fv){
				ans--;
				fa[fv][j] = fu;
			}
			else{
				break;
			}
		}
		if (i >= n){
			cout << ans << '\n';
		}
	}
}

01-19

LGP2619 [国家集训队] Tree I

原题链接:[国家集训队] Tree I

分析

哦,这个题啊......击球手很早之前给我推过......呃......我忘了二分啥了......难道是二分边权?

并不完全......我们发现,如果给所有的白边加上一个偏移量 d d d,会使得求 MST ⁡ \operatorname{MST} MST 的过程中更倾向于选择黑边,所以,通过控制 d d d 的大小,可以间接控制白边的数量,那就二分出 d d d 即可。这个单调性比较显然吧......

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int V, E, need;
struct node{
	int s, t, c, col;
}e[N];
int fa[N];
int find(int x){
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int cnt, sum, w;
bool cmp(node x, node y){
	if (x.c != y.c)
		return x.c < y.c;
	return x.col < y.col;
}
void Kruskal(){
	for (int i = 0; i <= V + 1; i++)
		fa[i] = i;
	sort(e + 1, e + E + 1, cmp);
	for (int i = 1; i <= E; i++){
		int u = e[i].s, v = e[i].t;
		int fu = find(u), fv = find(v);
		if (fu == fv)
			continue;
		else{
			fa[fu] = fv;
			cnt++;
			sum += e[i].c;
			w += (e[i].col == 0);
		}
		if (cnt == V - 1){
			return ;
		}
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> V >> E >> need;
	for (int i = 1; i <= E; i++){
		cin >> e[i].s >> e[i].t >> e[i].c >> e[i].col;
		e[i].s++;
		e[i].t++;
	}
	int l = -101, r = 101;
	int ans = 0;
	while (l <= r){
		int mid = (l + r) >> 1;
		for (int i = 1; i <= E; i++){
			if (e[i].col == 0){
				e[i].c += mid;
			}
		}
		sum = cnt = w = 0;
		Kruskal();
		if (w >= need){
			l = mid + 1;
			ans = sum - need * mid;
		}
		else{
			r = mid - 1;
		}
		for (int i = 1; i <= E; i++){
			if (e[i].col == 0){
				e[i].c -= mid;
			}
		}
	}
	cout << ans;
}

CF1550F Jumping Around

原题链接1:Jumping Around

原题链接2:F. Jumping Around

分析

这和最小生成树有毛线关系?你要是按照这个建图的话,可能是个图论,但是 MST ⁡ \operatorname{MST} MST 在哪?这不是个可达性问题吗?如果转化为图,就是当且仅当两点之间存在距离 l e n len len,使得 d − k ≤ l e n ≤ d + k d-k\le len\le d+k d−k≤len≤d+k,那么连边,然后,整一个可达性即可(似乎传递闭包很好, O ( q n 3 ) O(qn^3) O(qn3) 没得跑)。

那咋办?转化到瓶颈......就是 s s s 到 i i i 的瓶颈是不是 ≤ d \le d ≤d。 i i i, j j j 之间边权为 ∣ ∣ a i − a j ∣ − d ∣ ||a_i-a_j|-d| ∣∣ai−aj∣−d∣,考虑能否建出 MST ⁡ \operatorname{MST} MST,即每次考虑连通块内可以向外连出的最小边,维护一个 s e t set set,找出每个连通块向外的最小边, B o r u ˚ v k a ⁡ \operatorname{Borůvka} Boru˚vka 启动!大家猜我会 B o r u ˚ v k a ⁡ \operatorname{Borůvka} Boru˚vka 吗?那还不去学?似乎原理比较简单,但是,代码并非简单吧......

产生一个小问题,为啥我不直接判可达性?复杂度好像不是那么劣吧......

我要开始 he 了。

好像理解了一部分......

我理解了!就是......就是......难以名状吧......就是那三种求 MST ⁡ \operatorname{MST} MST 的算法的大杂烩!

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005, M = 1000005;
int n, q, s, d, a[N], buc[M], idx, k;
int fa[N], nxt[N], w[N];
pair<int, int> gnxt[N];
vector<int> g[N];
vector<pair<int, int>> e[N];
set<int> s1;
set<int, greater<int>> s2;
int calc(int x, int y){
    return abs(abs(a[x] - a[y]) - d);
}
int getfa(int x){
    return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
void merge(int x, int y){
    int fx = getfa(x), fy = getfa(y);
    if (fx == fy)
        return;
    fa[fy] = fx;
    e[x].push_back({y, calc(x, y)});
    e[y].push_back({x, calc(x, y)});
}
bool check1(){
    for (int i = 2; i <= n; i++){
        if (getfa(i) != getfa(1)){
            return true;
        }
    }
    return false;
}
void check2(int x, int y){
    if (!nxt[x]){
        nxt[x] = y;
    }
    else if (calc(x, y) < calc(x, nxt[x])){
        nxt[x] = y;
    }
}
void dfs(int u, int fa){
    for (auto tmp : e[u]){
        int v = tmp.first;
        if (v == fa)
            continue;
        w[v] = max(w[u], tmp.second);
        dfs(v, u);
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q >> s >> d;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        buc[a[i]] = i;
        fa[i] = i;
        s1.insert(a[i]);
        s2.insert(a[i]);
    }
    while (check1()){
        for (int i = 1; i <= n; i++){
            nxt[i] = 0;
            gnxt[i] = {0, 0};
            g[i].clear();
        }
        for (int i = 1; i <= n; i++){
            g[getfa(i)].push_back(i);
        }
        for (int block = 1; block <= n; block++){
            if (g[block].empty()) 
                continue;
            for (auto v : g[block]){
                s1.erase(a[v]);
                s2.erase(a[v]);
            }
            for (auto u : g[block]){
                auto it1 = s1.lower_bound(a[u] + d);
                if (it1 != s1.end()){
                    int v_val = *it1;
                    int v = buc[v_val];
                    check2(u, v);
                }
                auto it2 = s2.lower_bound(a[u] + d);
                if (it2 != s2.end()){
                    int v_val = *it2;
                    int v = buc[v_val];
                    check2(u, v);
                }
                auto it3 = s1.lower_bound(a[u] - d);
                if (it3 != s1.end()){
                    int v_val = *it3;
                    int v = buc[v_val];
                    check2(u, v);
                }
                auto it4 = s2.lower_bound(a[u] - d);
                if (it4 != s2.end()){
                    int v_val = *it4;
                    int v = buc[v_val];
                    check2(u, v);
                }
                if (nxt[u] && (gnxt[block].second == 0 || 
                    calc(gnxt[block].first, gnxt[block].second) > calc(u, nxt[u]))){
                    gnxt[block] = {u, nxt[u]};
                }
            }
            for (auto v : g[block]){
                s1.insert(a[v]);
                s2.insert(a[v]);
            }
        }
        for (int block = 1; block <= n; block++){
            if (gnxt[block].second == 0) 
                continue;
            int u = gnxt[block].first, v = gnxt[block].second;
            merge(u, v);
        }
    }
    dfs(s, 0);
    for (int i = 1; i <= q; i++){
        cin >> idx >> k;
        if (w[idx] <= k)
            cout << "Yes" << '\n';
        else
            cout << "No" << '\n';
    }
}

记得把那道二分的扔进 Trick 本里!

相关推荐
程序员-King.2 小时前
day153—回溯—子集(LeetCode-78)
算法·leetcode·回溯·递归
玖釉-2 小时前
[Vulkan 学习之路] 29 - 加载模型 (Loading Models)
c++·windows·图形渲染
MicroTech20252 小时前
突破C2Q瓶颈,MLGO微算法科技高性能可重构计算机实现量子算法真实级仿真,推动量子仿真进入新阶段
科技·算法·重构
GLDbalala2 小时前
GPU PRO 4 - 5.2 Kinect Programming with Direct3D 11 笔记
笔记
Elias不吃糖2 小时前
Markdown 基础语法学习笔记
笔记·学习·markdown
日更嵌入式的打工仔2 小时前
嵌入式系统设计师软考个人笔记<2>
笔记
HelloWorld1024!2 小时前
C++中链表的虚拟头结点:应用场景与使用时机
网络·c++·链表
Qhumaing2 小时前
Java学习——第五章 异常处理与输入输出流笔记
java·笔记·学习
fakerth2 小时前
【C++】【Linux】从零实现日志库:LogLib 设计与实现详解
c++·日志库