算法分析与设计 - 作业8

问题一

要求一串整数流在尽可能短的时间内求得已读取的数字流中比 x 小的元素个数。注意是数据流的问题,所以要考虑新元素的加入与求解个数的影响。

强制在线顺序对(排名)。

解法一

权值树状数组维护权值的出现次数。

单点修改,区间查询小于给定值的数的数量。

空间复杂度 \(O(n)\) 级别;单点修改与区间查询均为 \(O(\log v)\) 级别,总时间复杂度 \(O(n\log v)\) 级别。

//知识点:树状数组
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kMaxn = 1e6 + 10;
const int kInf = 1e6;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
namespace Bit {
  #define lowbit(x) (x&-x)
  int Lim;
  LL t[kMaxn];
  void Init(int lim_) {
    Lim = lim_;
  }
  void Insert(int pos_, LL val_) {
    for (int i = pos_; i <= Lim; i += lowbit(i)) {
      t[i] += val_;
    }
  }
  LL Sum(int pos_) {
    LL ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      ret += t[i];
    }
    return ret;
  }
  #undef lowbit
}
//=============================================================
int main() {
  n = read();
  Bit::Init(kInf);
  for (int i = 1; i <= n; ++ i) {
    int x = read();
    printf("%lld\n", Bit::Sum(x - 1));
    Bit::Insert(x, 1);
  }
  return 0;
}

解法二

对值域进行分块,维护每块的和。

修改时单点修改出现次数,修改所在块的和。

查询时对查询区间先查完整块的和,再单点查询不属于完整块的部分。

空间复杂度 \(O(n)\) 级别,单点修改 \(O(1)\) 级别,区间查询 \(O(m + \frac{v}{m})\) 级别(\(m\) 为块大小),根据均值不等式 \(m + \frac{v}{m}\ge 2\sqrt{v}\),则块大小应取 \(\sqrt v\) 级别。

总时间复杂度 \(O(n\sqrt v)\) 级别。

//知识点:分块
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <cstring>
#define LL long long
const int kN = 5e4 + 10;
//=============================================================
int n, m;
int block_size, block_num, L[kN], R[kN], bel[kN];
LL a[kN], sum[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmin(LL &fir, LL sec) {
  if (sec < fir) fir = sec;
}
void PrepareBlock() {
  block_size = sqrt(n); //根据实际要求选择一个合适的大小。
  block_num = n / block_size; 
  for (int i = 1; i <= block_num; ++ i) { //分配块左右边界。
    L[i] = (i - 1) * block_size + 1;
    R[i] = i * block_size;
  }
  if (R[block_num] < n) { //最后的一个较小的块。
    ++ block_num;
    L[block_num] = R[block_num - 1] + 1;
    R[block_num] = n;
  }
  //分配元素所属的块编号。
  for (int i = 1; i <= block_num; ++ i) {
    for (int j = L[i]; j <= R[i]; ++ j) {
      bel[j] = i;
    }
  }
}
void Insert(int pos_, int val_) {
  a[pos_] += val_;
  sum[bel[pos_]] += val_;
}
int Query(int l_, int r_) {
  if (l_ > r_) return 0;

  int bell = bel[l_], belr = bel[r_], ret = 0;
  if (bell == belr) {
    for (int i = l_; i <= r_; ++ i) ret += a[i];
    return ret;
  }
  for (int i = bell + 1; i <= belr - 1; ++ i) ret += sum[i];
  for (int i = l_; i <= R[bell]; ++ i) ret += a[i];
  for (int i = L[belr]; i <= r_; ++ i) ret += a[i];
  return ret;
}
//=============================================================
int main() { 
  n = read();
  PrepareBlock();
  for (int i = 1; i <= n; ++ i) {
    int x = read();
    printf("%d\n", Query(1, x - 1));
    Insert(x, 1);
  }

  return 0; 
}

解法三

权值线段树维护权值的出现次数。

单点修改,区间查询小于给定值的数的数量。

空间复杂度 \(O(v\log v)\) 级别;单点修改与区间查询均为 \(O(\log v)\) 级别,总时间复杂度 \(O(n\log v)\) 级别。

//知识点:线段树 
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kMaxn = 1e5 + 10;
const int kInf = 1e5;
//=============================================================
int n, m, a[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  LL sum[kMaxn << 2], tag[kMaxn << 2];
  void Pushup(int now_) {
    sum[now_] = sum[ls] + sum[rs];
  }
  void Pushdown(int now_, int L_, int R_) {
    sum[ls] += 1ll * tag[now_] * (mid - L_ + 1);
    sum[rs] += 1ll * tag[now_] * (R_ - mid);
    tag[ls] += tag[now_];
    tag[rs] += tag[now_];
    tag[now_] = 0ll;
  }
  void Build(int now_, int L_, int R_) {
    if (L_ == R_) {
      sum[now_] = a[L_];
      tag[now_] = 0ll;
      return ;
    }
    Build(ls, L_, mid);
    Build(rs, mid + 1, R_);
    Pushup(now_); 
  }
  void Modify(int now_, int L_, int R_, int l_, int r_, LL val_) {
    if (l_ <= L_ and R_ <= r_) {
      sum[now_] += 1ll * (R_ - L_ + 1) * val_;
      tag[now_] += val_;
      return ;
    }
    Pushdown(now_, L_, R_);
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
    Pushup(now_);
  }
  LL Query(int now_, int L_, int R_, int l_, int r_) {
    if (l_ > r_) return 0;

    if (l_ <= L_ and R_ <= r_) return sum[now_];
    Pushdown(now_, L_, R_);
    LL ret = 0;
    if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_);
    if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_);
    return ret;
  }
  #undef ls
  #undef rs
  #undef mid
}
//=============================================================
int main() {
  Seg::Build(1, 1, kInf);

  n = read();
  for (int i = 1; i <= n; ++ i) {
    int x = read();
    printf("%lld\n", Seg::Query(1, 1, kInf, 1, x - 1));
    Seg::Modify(1, 1, kInf, x, x, 1);
  }
  return 0;
}

解法四

考虑使用平衡树进行维护,则仅需支持动态插入元素,查询元素排名。

使用 Splay 实现。

空间复杂度 \(O(n)\) 级别,单次插入查询时间复杂度 \(O(\log n)\) 级别,总时间复杂度 \(O(n\log n)\) 级别。

//知识点:Splay
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace Splay {
  #define f fa[now_]
  #define ls son[now_][0]
  #define rs son[now_][1]
  const int kMaxNode = 1e6 + 10;
  int root, node_num, fa[kMaxNode], son[kMaxNode][2];
  int val[kMaxNode], cnt[kMaxNode], siz[kMaxNode];
  int top, bin[kMaxNode];
  void Pushup(int now_) { //更新节点大小信息
    if (!now_) return ;
    siz[now_] = cnt[now_];
    if (ls) siz[now_] += siz[ls];
    if (rs) siz[now_] += siz[rs];
  }
  int WhichSon(int now_) { //获得儿子类型
    return now_ == son[f][1];
  }
  void Clear(int now_) { //清空节点,同时进行垃圾回收
    f = ls = rs = val[now_] = cnt[now_] = siz[now_] = 0;
    bin[++ top] = now_;
  }
  int NewNode(int fa_, int val_) { //建立新节点
    int now_ = top ? bin[top --] : ++ node_num; //优先调用垃圾堆
    f = fa_, val[now_] = val_, siz[now_] = cnt[now_] = 1;
    return now_;
  }
  void Rotate(int now_) { //旋转操作
    int fa_ = f, whichson = WhichSon(now_);
    if (fa[f]) son[fa[f]][WhichSon(f)] = now_;
    f = fa[f];

    son[fa_][whichson] = son[now_][whichson ^ 1];
    fa[son[fa_][whichson]] = fa_;

    son[now_][whichson ^ 1] = fa_;
    fa[fa_] = now_;
    Pushup(fa_), Pushup(now_);
  }
  void Splay(int now_) { //旋转到根操作
    for (; f != 0; Rotate(now_)) {
      if (fa[f]) Rotate(WhichSon(now_) == WhichSon(f) ? f : now_);
    }
    root = now_;
  }
  void Insert(int now_, int fa_, int val_) { //插入操作
    if (now_ && val[now_] != val_) {
      Insert(son[now_][val[now_] < val_], now_, val_);
      return ;
    }
    if (val[now_] == val_) ++ cnt[now_];
    if (!now_) {
      now_ = NewNode(fa_, val_);
      if (f) son[f][val[f] < val_] = now_;
    }
    Pushup(now_), Pushup(f), Splay(now_); //注意旋转到根
    return ;
  }
  int Find(int now_, int val_) { //将权值 val 对应节点旋转至根。在平衡树上二分。
    if (!now_) return false; //不存在
    if (val_ < val[now_]) return Find(ls, val_);
    if (val_ == val[now_]) {
      Splay(now_);
      return true;
    }
    return Find(rs, val_);
  }
  void Delete(int val_) { //删除一个权值 val 
    if (!Find(root, val_)) return ; //将 val 转到根
    if (cnt[root] > 1) {
      -- cnt[root];
      Pushup(root);
      return ;
    }
    int oldroot = root;
    if (!son[root][0] && !son[root][1]) {
      root = 0;
    } else if (!son[root][0]) { 
      root = son[root][1], fa[root] = 0;
    } else if (!son[root][1]) {
      root = son[root][0], fa[root] = 0;
    } else if (son[root][0] && son[root][1]) {
      //将中序遍历中 root 前的一个元素作为新的 root。该元素即为 root 左子树中最大的元素。
      int leftmax = son[root][0];
      while (son[leftmax][1]) leftmax = son[leftmax][1];
      Splay(leftmax); //转到根
      son[root][1] = son[oldroot][1], fa[son[root][1]] = root; //继承信息
    }
    Clear(oldroot), Pushup(root);
  }
  int QueryRank(int val_) { //查询 val_ 的排名
    Insert(root, 0, val_); //先插入,将其转到根,查询左子树大小
    int ret = siz[son[root][0]] + 1;
    Delete(val_);
    return ret;
  }
}
//=============================================================
int main() { 
  int n = read();
  for (int i = 1; i <= n; ++ i) {
    int x = read();
    printf("%d\n", Splay::QueryRank(x) - 1);
    Splay::Insert(Splay::root, 0, x);
  }
  return 0; 
}

问题二

顺时针打印旋转矩阵元素。

蛇形矩阵问题,模拟即可。

总时空复杂度均为 \(O(nm)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e3 + 10;
const int ex[4] = {0, 1, 0, -1};
const int ey[4] = {1, 0, -1, 0};
//=============================================================
int n, m, ans[kN][kN];
//=============================================================
bool check(int x_, int y_) {
  return 1 <= x_ && x_ <= n &&
         1 <= y_ && y_ <= m &&
         ans[x_][y_] == 0;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> m;
  
  for (int i = 1, x = 1, y = 1, dir = 0; i <= n * m; ++ i) {
    // std::cout << x << " " << y << "\n";
    ans[x][y] = i;
    if (!check(x + ex[dir], y + ey[dir])) dir = (dir + 1) % 4;
    x = x + ex[dir], y = y + ey[dir];
  }
  
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= m; ++ j) {
      std::cout << ans[i][j] << " ";
    }
    std::cout << "\n";
  }
  return 0;
}
posted @ 2024-04-21 17:33  Rainycolor  阅读(27)  评论(0编辑  收藏  举报