YBTOJ 4.6倍增问题
A.查找编号
一眼二分
但是也可以拿倍增做
我们维护这个点往后
然后从大到小枚举
如果往后
否则不跳
代码我写的二分就不放了
B.开车旅行
说句题外话 如果你是从 LCA 那章一路刷下来的 你会经历紫题三连发
再吐槽一句 这题题干是真长啊 读着头疼
看起来这两问非常的难 尤其第一问更没什么思路
但是看第二问 显然要做到
那么对于第一问 我们如果枚举起点 就是
那么问题就在于如何做到
进一步发现 实际上如果我选定起点 后续的过程都是固定的
那我就可以倍增预处理出经过
并且因为
注意当
又因为要判总距离 所以再记录一个
upd:感觉预处理出前缀和也能做(?
还有一个问题 如何找这个城市之后最高 / 第二高的城市
暴力查找
对此我们可以先排序 然后维护一个双向链表
然后把
当然也可以使用
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define ll long long
#define db double
using namespace std;
const int N = 1e5 + 0721;
const int inf = 0x7ffffffffffffff;
int f[2][N][21];
ll disa[2][N][21];
ll disb[2][N][21];
int h[N];
int n, m;
struct node {
int id, hi;
friend bool operator<(node a, node b) { return a.hi < b.hi; }
};
multiset<node> s;
void init(void) {
h[0] = inf, h[n + 1] = -inf;
s.insert((node){ 0, inf });
s.insert((node){ 0, inf });
s.insert((node){ n + 1, -inf });
s.insert((node){ n + 1, -inf });
set<node>::iterator p;
for (int i = n; i >= 1; --i) {
int nxta, nxtb;
s.insert((node){ i, h[i] });
p = s.lower_bound((node){ i, h[i] });
// cout<<(*p).id<<endl;
++p;
int h1 = (*p).id;
--p, --p;
int q1 = (*p).id;
++p;
// cout<<h1<<" "<<q1<<endl;
// cout<<abs(h[q1] - h[i])<<" "<<abs(h[h1] - h[i])<<endl;
if (abs(h[q1] - h[i]) <= abs(h[h1] - h[i])) {
// cout<<"nxb= "<<q1<<endl;
nxtb = q1;
--p, --p;
int q2 = (*p).id;
if (abs(h[q2] - h[i]) <= abs(h[h1] - h[i]))
nxta = q2;
else
nxta = h1;
}
else {
// cout<<"nxtb = "<<h1<<endl;
nxtb = h1;
++p, ++p;
int h2 = (*p).id;
if (abs(h[q1] - h[i]) <= abs(h[h2] - h[i]))
nxta = q1;
else
nxta = h2;
}
disa[0][i][0] = abs(h[nxta] - h[i]);
disb[1][i][0] = abs(h[nxtb] - h[i]);
f[0][i][0] = nxta;
f[1][i][0] = nxtb;
// cout<<i<<" "<<disa[0][i][0]<<" "<<disb[1][i][0]<<endl;
// cout<<"i = "<<i<<"时,"<<nxta<<" "<<nxtb<<endl;
}
}
void dp(void) {
for (int j = 1; j <= 20; ++j) {
for (int i = 1; i <= n; ++i) {
if (j == 1) {
f[0][i][1] = f[1][f[0][i][0]][0];
f[1][i][1] = f[0][f[1][i][0]][0];
disa[0][i][1] = disa[0][i][0] + disa[1][f[0][i][0]][0];
disa[1][i][1] = disa[1][i][0] + disa[0][f[1][i][0]][0];
disb[0][i][1] = disb[0][i][0] + disb[1][f[0][i][0]][0];
disb[1][i][1] = disb[1][i][0] + disb[0][f[1][i][0]][0];
}
else {
f[0][i][j] = f[0][f[0][i][j - 1]][j - 1];
f[1][i][j] = f[1][f[1][i][j - 1]][j - 1];
disa[0][i][j] = disa[0][i][j - 1] + disa[0][f[0][i][j - 1]][j - 1];
disa[1][i][j] = disa[1][i][j - 1] + disa[1][f[1][i][j - 1]][j - 1];
disb[0][i][j] = disb[0][i][j - 1] + disb[0][f[0][i][j - 1]][j - 1];
disb[1][i][j] = disb[1][i][j - 1] + disb[1][f[1][i][j - 1]][j - 1];
}
}
}
}
ll la, lb;
void cal(int s, ll x) {
// cout<<"s="<<s<<", x="<<x<<endl;
la = lb = 0;
for (int j = 20; j >= 0; --j) {
// cout<<x<<" "<<j<<" "<<disa[0][x][j]<<" "<<x<<endl;
if (f[0][s][j] && disa[0][s][j] + disb[0][s][j] <= x) {
x -= (disa[0][s][j] + disb[0][s][j]);
la += disa[0][s][j], lb += disb[0][s][j];
s = f[0][s][j];
}
}
}
signed main() {
// freopen("1.txt", "r", stdin);
scanf("%lld", &n);
for (int i = 1; i <= n; ++i) scanf("%lld", &h[i]);
init();
dp();
ll x0;
scanf("%lld", &x0);
db minans, ans;
int ansid;
cal(1, x0);
ansid = 1, minans = db(la) / db(lb);
for (int i = 2; i <= n; ++i) {
cal(i, x0);
ans = db(la) / db(lb);
if (ans < minans) {
minans = ans;
ansid = i;
} else if (ans == minans && h[i] > h[ansid])
ansid = i;
}
printf("%lld\n",ansid);
scanf("%lld", &m);
for (int i = 1; i <= m; ++i) {
int s;
ll x;
scanf("%lld%lld", &s, &x);
cal(s, x);
printf("%lld %lld\n",la,lb);
}
return 0;
C.跑路上班
刚开始想的是找一条路使其二进制拆分出来的 1 的数量最少 但是根本不可做
考虑倍增性质
如果我可以用
换言之 我们就可以用
所以我们用
转移时我们直接像上面那样枚举中间点转移即可
这样我们就可以处理出用
然后跑最短路即可 偷懒可以直接跑 floyd
点击查看代码
#include <bits/stdc++.h>
using namespace std;
namespace steven24 {
const int N = 52;
const int M = 1e4 + 0721;
int dis[N][N];
bool f[N][N][66];
int n, m;
void main() {
memset(dis, 0x3f, sizeof dis);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int x, y;
scanf("%d%d", &x, &y);
f[x][y][0] = 1;
dis[x][y] = 1;
}
for (int j = 1; j <= 64; ++j) {
for (int v = 1; v <= n; ++v) {
for (int u = 1; u <= n; ++u) {
for (int s = 1; s <= n; ++s) {
if (f[u][v][j - 1] && f[v][s][j - 1]) {
f[u][s][j] = 1;
dis[u][s] = 1;
}
}
}
}
}
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
printf("%d\n", dis[1][n]);
}
}
int main() {
steven24::main();
return 0;
}
/*
4 4
1 1
1 2
2 3
3 4
*/
D.图上查询
因为
看出来倍增这题就没啥了( 分别记录从当前点出发跳
然后统计答案的时候再开个
有个坑点就是根据题目描述 点的编号是
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
namespace steven24 {
const int N = 1e5 + 0721;
ll f[N][36];
int minn[N][36];
int to[N][36];
ll s[N];
int m[N], now[N];
int n;
ll k;
ll calc(int x) {
if (x <= 20) return 1ll * (1 << x);
else return 1ll * (1 << 20) * (1 << (x - 20));
}
void main() {
scanf("%d%lld", &n, &k);
for (int i = 1; i <= n; ++i) {
scanf("%d", &to[i][0]);
++to[i][0];
}
for (int i = 1; i <= n; ++i) {
scanf("%d", &minn[i][0]);
f[i][0] = minn[i][0];
}
for (int j = 1; j <= 35; ++j) {
for (int i = 1; i <= n; ++i) {
to[i][j] = to[to[i][j - 1]][j - 1];
// cout << i << " " << j << " " << to[i][j] << "\n";
minn[i][j] = min(minn[i][j - 1], minn[to[i][j - 1]][j - 1]);
f[i][j] = f[i][j - 1] + f[to[i][j - 1]][j - 1];
}
}
memset(m, 0x3f, sizeof m);
for (int i = 1; i <= n; ++i) now[i] = i;
for (int j = 35; j >= 0; --j) {
if (k & calc(j)) {
for (int i = 1; i <= n; ++i) {
s[i] += f[now[i]][j];
m[i] = min(m[i], minn[now[i]][j]);
now[i] = to[now[i]][j];
}
}
}
for (int i = 1; i <= n; ++i) printf("%lld %d\n", s[i], m[i]);
}
}
int main() {
steven24::main();
return 0;
}
/*
7 3
1 2 3 4 3 2 6
6 3 1 4 2 2 3
*/
E.小明聚会
首先很容易想到一个 DP:
设
其中
那么转移的时候我们枚举
我们发现这个
所以我们设
那么当我们 dfs 到点
然后进行上述转移就可以压到
点击查看代码
#include <bits/stdc++.h>
using namespace std;
namespace steven24 {
const int N = 1e5 + 0721;
const int inf = 0x7fffffff;
int fa[21][N], minn[21][N];
int f[N];
int head[N], nxt[N], to[N], cnt;
int n, m, q;
struct node {
int w, k;
};
vector<node> a[N];
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
inline void add_edge(int x, int y) {
to[++cnt] = y;
nxt[cnt] = head[x];
head[x] = cnt;
}
void init() {
for (int j = 1; j <= 20; ++j) {
for (int i = 1; i <= n; ++i) fa[j][i] = fa[j - 1][fa[j - 1][i]];
}
}
void dfs(int x) {
minn[0][x] = f[fa[0][x]];
for (int j = 1; j <= 20; ++j) minn[j][x] = min(minn[j - 1][x], minn[j - 1][fa[j - 1][x]]);
for (auto nod : a[x]) {
int k = nod.k, w = nod.w;
int tmp = inf;
int xx = x;
for (int j = 20; j >= 0; --j) {
if (k >= (1 << j)) {
k -= (1 << j);
tmp = min(tmp, minn[j][xx]);
xx = fa[j][xx];
}
}
f[x] = min(f[x], tmp + w);
// cout << "x: " << x << " " << f[x] << "\n";
}
for (int i = head[x]; i; i = nxt[i]) {
int y = to[i];
dfs(y);
}
}
void main() {
n = read(), m = read();
for (int i = 1; i < n; ++i) {
int x, y;
x = read(), y = read();
fa[0][x] = y;
add_edge(y, x);
}
init();
for (int i = 1; i <= m; ++i) {
int v, k, w;
v = read(), k = read(), w = read();
a[v].push_back((node){w, k});
}
memset(f, 0x3f, sizeof f);
f[1] = 0;
dfs(1);
q = read();
while (q--) {
int x;
x = read();
printf("%d\n", f[x]);
}
}
}
int main() {
steven24::main();
return 0;
}
/*
7 7
3 1
2 1
7 6
6 3
5 3
4 3
7 2 3
7 1 1
2 3 5
3 6 2
4 2 4
5 3 10
6 1 20
3
5
6
7
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下