基础算法随笔

基础算法

世界中のすべての人間に好かれるなんて気持ち悪いよ

だけど一つになれない教室で 君と二人 手を繋いでいたいの

数の暴力に白旗をあげて 悪感情を殺してハイチーズ

ポストトゥルースの甘いディープキス エロく歪んでるラブアンドピース

分治

幸せ自慢はダメ? 不幸嘆いてもダメ?

図々しい言葉を避け 明るい未来のため

重要的思想。

分治应用

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 送礼物

折半搜索板子题。

将礼物分为均分两类,对第一类枚举所有情况,统计一下。

然后第二类二分查找。

set T了,最好还是卡卡常

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)\) 地处理。

\[f(u, i) = w(u, v) + w(v,i)\\ f(v, i) = w(i, u) + w(u, v) \]

\(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;
}
posted @ 2025-07-30 21:58  FRZ_29  阅读(9)  评论(0)    收藏  举报
2025-7-11 11:06:15 TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 FRZ - 1801534592@qq.com
Illustration from たとえ,by Rella