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 本里!