基础算法随笔
基础算法
世界中のすべての人間に好かれるなんて気持ち悪いよ
だけど一つになれない教室で 君と二人 手を繋いでいたいの
数の暴力に白旗をあげて 悪感情を殺してハイチーズ
ポストトゥルースの甘いディープキス エロく歪んでるラブアンドピース
分治
幸せ自慢はダメ? 不幸嘆いてもダメ?
図々しい言葉を避け 明るい未来のため
重要的思想。
分治应用
P4423 [BJWC2011] 最小三角形
大概是平面最近点对升级版。
\(\mathcal{O}(n^3)\) 暴力做法显然。考虑优化。
假设现在已经处理出左右两边的最小三角形,已得最小边长至少为 \(d\) ,考虑合并。
显然,到中轴线距离大于 \(d/2\) 的点是没用的,这大大降低了暴力的范围。
将有用的点排序,从小到大地暴力枚举每个点,显然如果枚举到的三个点有两个竖直/水平距离大于 \(d/2\),构成的三角形是没有贡献的,这又大大降低了暴力的范围。
于是就能过了。
实际上枚举 \(20\) 个点即可,所以很快。时间复杂度是 \(\mathcal{O}(n\log n)\),带一个比较大的常数。
code
const int N = 2e5 + 5;
const double inf = 1e200;
int n;
struct node { double x, y; } a[N];
double dist(node i, node j) {
double x = abs(i.x - j.x), y = abs(i.y - j.y);
return sqrt(x * x + y * y);
}
bool cmpy(node a, node b) {
return a.y < b.y;
}
double solve(int l, int r) {
if (r - l + 1 < 3) return inf;
if (r - l + 1 == 3) return dist(a[l], a[l + 1]) + dist(a[l + 1], a[r]) + dist(a[l], a[r]);
int mid = (l + r) >> 1;
double d = min(solve(l, mid), solve(mid + 1, r));
vector<node> vec;
rep(i, l, r) if (abs(a[i].x - a[mid].x) <= d / 2.0) vec.push_back(a[i]);
if (vec.size() == 0) return d;
sort(vec.begin(), vec.end(), cmpy);
rep(i, 0, vec.size() - 1) rep(j, i + 1, vec.size() - 1) {
if (abs(vec[i].y - vec[j].y) > d / 2) break;
rep(k, j + 1, vec.size() - 1) {
if (i != j && i != k && j != k) {
d = min(d, dist(vec[i], vec[j]) + dist(vec[i], vec[k]) + dist(vec[j], vec[k]));
}
}
}
return d;
}
bool cmp(node a, node b) {
return a.x < b.x;
}
int main() {
read(n);
rep(i, 1, n) read(a[i].x, a[i].y);
sort(a + 1, a + n + 1, cmp);
printf("%0.6lf", solve(1, n));
return 0;
}
// START AT 2025 / 07 / 07 09 : 13 : 37
搜索
さんはい 「この世には息もできない人が沢山いるんですよ」
さんはい 「この世には息もできない人が沢山いるんですよ」
あちらが立てば こちらが立たず 譲り 奪い 守り 行き違い
地雷原で立ち止まり 大人しく犬になるんだワン
大概很多题目都能用搜索拿点分,是很有用的算法。
折半搜索
先搜一半,然后根据另一半查找。
P10484 送礼物
折半搜索板子题。
将礼物分为均分两类,对第一类枚举所有情况,统计一下。
然后第二类二分查找。
用
setT了,最好还是卡卡常
code
const int N = 50;
int w, n, g[N];
i64 s[1 << 24], cnt;
i64 ans;
void dfs(int dep, int goal, int k, i64 sum) {
if (dep == goal + 1) {
if (k == 1) s[++cnt] = sum;
else {
int pos = upper_bound(s + 1, s + cnt + 1, w - sum) - s - 1;
ans = max(ans, s[pos] + sum);
}
return;
}
dfs(dep + 1, goal, k, sum);
if (sum + g[dep] > w) return;
dfs(dep + 1, goal, k, sum + g[dep]);
}
int main() {
read(w, n);
rep(i, 1, n) read(g[i]);
int mid = (1 + n) >> 1;
dfs(1, mid, 1, 0);
sort(s + 1, s + cnt);
dfs(mid + 1, n, 2, 0);
write(ans);
return 0;
}
P8906 [USACO22DEC] Breakdown P
不容易想到折半搜索吧。我觉得怎么想都不自然
倒序处理,删边变加边,比较好想。考虑处理最短路径。
正序倒序最多分别分到 \(k = 4\)。\(k = 1\) 容易处理。
\(k = 2\) 时考虑全局维护 \(f(i, j)\) ,表示 \(i \rightarrow j\) 经过两条路的最短路径。
这是好处理的也不是很显然,因为加边 \((u, v)\) 只与 \(f(u, i), f(i, v), i\in V\) 有关。\(\mathcal{O}(n)\) 地处理。
则 \(1\rightarrow x\) 的路径长为 \(f(1, x)\),\(n\) 同理。
然后 \(k = 3\),\(k = 4\) 同理。对于 \(x\) 来说,\(1\rightarrow x\) 经过 \(3\) 条路径的长最小为 \(\min\{f(1, i) + w(i, x)\}\)。
\(k = 4\) 把 \(w(i, x)\) 换为 \(f(i, x)\) 即可。
处理完合并即可。
时间复杂度比较神奇吧……
code
const int N = 305;
int n, k, e[N][N], u[N * N], v[N * N], ans[N * N];
struct node {
int st, e[N][N], f[N][N], h[N], k;
void init() {
memset(e, 0x3f, sizeof(e));
memset(f, 0x3f, sizeof(f));
memset(h, 0x3f, sizeof(h));
if (!k) h[st] = 0;
}
void add(int u, int v, int w) {
e[u][v] = w;
if (!k) return;
if (k == 1) {
if (u == st) h[v] = w;
return;
}
rep(i, 1, n) f[u][i] = min(e[u][v] + e[v][i], f[u][i]);
rep(i, 1, n) f[i][v] = min(e[i][u] + e[u][v], f[i][v]);
if (k == 2) {
rep(i, 1, n) h[i] = min(f[st][i], h[i]);
return;
}
auto p = (k == 3 ? e : f);
if (u == st) {
rep(i, 1, n) rep(j, 1, n) h[j] = min(f[st][i] + p[i][j], h[j]);
} else {
rep(i, 1, n)
h[u] = min(h[u], f[st][i] + p[i][u]),
h[v] = min(h[v], f[st][i] + p[i][v]),
h[i] = min(h[i], f[st][u] + p[u][i]),
h[i] = min(h[i], f[st][v] + p[v][i]);
}
}
} _1, _n;
int main() {
read(n, k);
rep(i, 1, n) rep(j, 1, n) read(e[i][j]);
rep(i, 1, n * n) read(u[i], v[i]);
_1.st = 1, _n.st = n;
_1.k = k / 2, _n.k = k - k / 2;
_1.init(); _n.init();
per(i, n * n, 1) {
ans[i] = 0x3f3f3f3f;
rep(j, 1, n) ans[i] = min(_1.h[j] + _n.h[j], ans[i]);
_1.add(u[i], v[i], e[u[i]][v[i]]);
_n.add(v[i], u[i], e[u[i]][v[i]]);
}
rep(i, 1, n * n) {
if (ans[i] == 0x3f3f3f3f) write(-1), puts("");
else write(ans[i]), puts("");
}
return 0;
}
迭代加深
限制 DFS 层数的搜索。适用于一些答案不会太大的搜索。
P2346 四子连棋
首先猜测答案不会太大,然后考虑 IDDFS。
剩下的就很基础了。
存在不用动的特殊情况
考虑特殊情况应该也是做题的一环
code
int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};
struct node {
int x, y;
} a, b;
char s[4][4];
bool check(char s[4][4]) {
rep(i, 0, 3) if (s[i][0] == s[i][1] && s[i][1] == s[i][2] && s[i][2] == s[i][3]) return 1;
rep(i, 0, 3) if (s[0][i] == s[1][i] && s[1][i] == s[2][i] && s[2][i] == s[3][i]) return 1;
if (s[1][1] == s[2][2] && s[0][0] == s[1][1] && s[2][2] == s[3][3]) return 1;
if (s[0][3] == s[1][2] && s[1][2] == s[2][1] && s[2][1] == s[3][0]) return 1;
return 0;
}
bool check(int x, int y) {
return x >= 0 && x <= 3 && y >= 0 && y <= 3;
}
bool dfs(node a, node b, int k, int dep, int goal, char s[4][4]) {
if (dep == goal + 1) return check(s);
rep(i, 0, 3) {
int nx = a.x + dx[i], ny = a.y + dy[i];
if (!check(nx, ny)) continue;
if (s[nx][ny] == 'O') continue;
if (k == 0 && s[nx][ny] == 'B') continue;
if (k == 1 && s[nx][ny] == 'W') continue;
if (k == 0) s[nx][ny] = 'O', s[a.x][a.y] = 'W';
else s[nx][ny] = 'O', s[a.x][a.y] = 'B';
if (dfs({nx, ny}, b, 1 - k, dep + 1, goal, s)) return 1;
if (k == 0) s[a.x][a.y] = 'O', s[nx][ny] = 'W';
else s[a.x][a.y] = 'O', s[nx][ny] = 'B';
}
rep(i, 0, 3) {
int nx = b.x + dx[i], ny = b.y + dy[i];
if (!check(nx, ny)) continue;
if (s[nx][ny] == 'O') continue;
if (k == 0 && s[nx][ny] == 'B') continue;
if (k == 1 && s[nx][ny] == 'W') continue;
if (k == 0) s[nx][ny] = 'O', s[b.x][b.y] = 'W';
else s[nx][ny] = 'O', s[b.x][b.y] = 'B';
if (dfs(a, {nx, ny}, 1 - k, dep + 1, goal, s)) return 1;
if (k == 0) s[b.x][b.y] = 'O', s[nx][ny] = 'W';
else s[b.x][b.y] = 'O', s[nx][ny] = 'B';
}
return 0;
}
int main() {
rep(i, 0, 3) cin >> s[i];
bool flag = 0;
rep(i, 0, 3) rep(j, 0, 3) if (s[i][j] == 'O') {
if (!flag) a = {i, j}, flag = 1;
else b = {i, j};
}
if (check(s)) return cout << 0 << endl, 0;
for(int i = 1; ; i++) if (dfs(a, b, 0, 1, i, s) || dfs(a, b, 1, 1, i, s)) return cout << i << endl, 0;
return 0;
}
倍增
ノンブレス ノンブレス ノンブレス ノンブレス・オブリージュ
I love you
息苦しい日々の水面下 ゆらゆらと煙る血の花
以成倍增长的方式处理问题。与二进制划分有关。
倍增应用
P1081 [NOIP 2012 提高组] 开车旅行
处理小A小B从某个城市出发下一个目的地是简单的。
因此可以用倍增处理出从某个城市出发 \(x\) 步的目的地。
时间复杂度 \(\mathcal{O}(n\log n)\)。
code
const int N = 1e5 + 5;
const int inf = 2e9 + 1;
struct node {
int id, h;
node(int id = 0, int h = 0) : id(id), h(h) {}
friend bool operator< (node a, node b) { return a.h < b.h; }
} a[N];
multiset<node> s;
int n, x0, ga[N], gb[N];
int m, S[N], x[N];
i64 f[20][N][2], da[20][N][2], db[20][N][2], la, lb;
void init() {
rep(i, 0, 1) s.insert(node(0, -inf)), s.insert(node(0, inf));
per(i, n, 1) {
node x = a[i];
s.insert(x);
auto it = s.find(x), _it = it;
node pre = *--it, _pre = *--it;
node Next = *++_it, _Next = *++_it;
if (abs(pre.h - x.h) > abs(Next.h - x.h)) {
gb[i] = Next.id;
if (abs(pre.h - x.h) > abs(_Next.h - x.h)) ga[i] = _Next.id;
else ga[i] = pre.id;
} else {
gb[i] = pre.id;
if (abs(Next.h - x.h) < abs(_pre.h - x.h)) ga[i] = Next.id;
else ga[i] = _pre.id;
}
}
}
void query(int s, int x) {
la = 0, lb = 0;
per(i, 19, 0) {
if (f[i][s][0] == 0) continue;
if (la + da[i][s][0] + lb + db[i][s][0] <= x) {
la += da[i][s][0], lb += db[i][s][0];
s = f[i][s][0];
}
}
}
int main() {
read(n);
rep(i, 1, n) a[i].id = i, read(a[i].h);
read(x0, m);
rep(i, 1, m) read(S[i], x[i]);
init();
rep(i, 1, n) {
f[0][i][0] = ga[i];
f[0][i][1] = gb[i];
da[0][i][0] = abs(a[ga[i]].h - a[i].h);
db[0][i][1] = abs(a[gb[i]].h - a[i].h);
}
rep(i, 1, 19) rep(j, 1, n) rep(k, 0, 1) {
if (i == 1) {
int mid = f[i - 1][j][k];
f[i][j][k] = f[i - 1][mid][1 - k];
da[i][j][k] = da[i - 1][j][k] + da[i - 1][mid][1 - k];
db[i][j][k] = db[i - 1][j][k] + db[i - 1][mid][1 - k];
} else {
int mid = f[i - 1][j][k];
f[i][j][k] = f[i - 1][mid][k];
da[i][j][k] = da[i - 1][j][k] + da[i - 1][mid][k];
db[i][j][k] = db[i - 1][j][k] + db[i - 1][mid][k];
}
}
double ans = 1e200;
bool flag = 1;
int p = 0;
rep(i, 1, n) {
query(i, x0);
if (lb == 0) continue;
if (ans > la * 1.0 / lb) {
ans = la * 1.0 / lb;
p = i;
flag = 0;
}
}
if (flag) {
auto it = s.rbegin(); it++, it++;
write((*it).id); puts("");
} else write(p), puts("");
rep(i, 1, m) {
query(S[i], x[i]);
write(la); putchar(' '); write(lb); puts("");
}
return 0;
}
浙公网安备 33010602011771号