算法分析与设计 - 作业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;
}