算法分析与设计 - 作业8
问题一
要求一串整数流在尽可能短的时间内求得已读取的数字流中比 x 小的元素个数。注意是数据流的问题,所以要考虑新元素的加入与求解个数的影响。
强制在线顺序对(排名)。
解法一
权值树状数组维护权值的出现次数。
单点修改,区间查询小于给定值的数的数量。
空间复杂度 级别;单点修改与区间查询均为 级别,总时间复杂度 级别。
复制//知识点:树状数组 /* 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; }
解法二
对值域进行分块,维护每块的和。
修改时单点修改出现次数,修改所在块的和。
查询时对查询区间先查完整块的和,再单点查询不属于完整块的部分。
空间复杂度 级别,单点修改 级别,区间查询 级别( 为块大小),根据均值不等式 ,则块大小应取 级别。
总时间复杂度 级别。
//知识点:分块 /* 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; }
解法三
权值线段树维护权值的出现次数。
单点修改,区间查询小于给定值的数的数量。
空间复杂度 级别;单点修改与区间查询均为 级别,总时间复杂度 级别。
//知识点:线段树 /* 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 实现。
空间复杂度 级别,单次插入查询时间复杂度 级别,总时间复杂度 级别。
//知识点: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; }
问题二
顺时针打印旋转矩阵元素。
蛇形矩阵问题,模拟即可。
总时空复杂度均为 级别。
// /* 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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现