Dec. 13th 2025

Dec. 13th 2025

A. 动态开点

为了解决空间不够的情况

我们就可以到了该用这个节点的时候再申请空间

代码:

void update(int &id, int d, int posi, int left, int right) {
    if (!id) id = ++cnt; // cnt if the number of nodes;
    if (left == right) {
        tree[id] = d; // +=
        return ;
    }
    int mid = (left + right) >> 1;
    if (posi <= mid) update(ls[id], d, posi, left, mid); // ls[] : left son, rs[] : right son
    else update(rs[id], d, mid + 1, right);
    maitain(id);
}

比较正常的 update

void update(ll posi, ll d, ll id = 1, ll left = 1, ll right = n) {
	if (left == right && left == posi) {
		tree[id] += d;
		return ;
	}
	ll mid = (left + right) >> 1;
	if (posi <= mid) update(posi, d, ls(id), left, mid);
	else 			 update(posi, d, rs(id), mid + 1, right);
	maintain(id);
}

就多了一行:

if (!id) id = ++cnt

然后将 ls()rs() 改成数组

!\(\textcolor{red}{\text{注意}}\)!:因为在 updateid 传的是地址,所以不能访问一个常量

例题


【模板】普通平衡树

P3369 【模板】普通平衡树
题目描述

您需要动态地维护一个可重集合 \(M\),并且提供以下操作:

  1. \(M\) 中插入一个数 \(x\)
  2. \(M\) 中删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
  3. 查询 \(M\) 中有多少个数比 \(x\) 小,并且将得到的答案加一。
  4. 查询如果将 \(M\) 从小到大排列后,排名位于第 \(x\) 位的数。
  5. 查询 \(M\)\(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. 查询 \(M\)\(x\) 的后继(后继定义为大于 \(x\),且最小的数)。

对于操作 \(3,5,6\)不保证当前可重集中存在数 \(x\)

对于操作 \(5,6\),保证答案一定存在。

输入格式

第一行为 \(n\),表示操作的个数,下面 \(n\) 行每行有两个数 \(\text{opt}\)\(x\)\(\text{opt}\) 表示操作的序号($ 1 \leq \text{opt} \leq 6 $)。

输出格式

对于操作 \(3,4,5,6\) 每行输出一个数,表示对应答案。

输入输出样例 #1

输入 #1

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出 #1

106465
84185
492737
说明/提示

【数据范围】

对于 \(100\%\) 的数据,\(1\le n \le 10^5\)\(|x| \le 10^7\)

来源:Tyvj1728,原名:普通平衡树。

在此鸣谢!


View Code
#include<bits/stdc++.h>
using namespace std;

using lf = double;
using ll = long long;
using ull = unsigned long long;

const int maxn = 1e5 + 5;
const ll MIN_X = -1e7;
const ll MAX_X = 1e7;

int n;
ll cnt = 1, root = 1;
ll a[maxn];
ll tree[maxn * 40];
ll ls[maxn * 40], rs[maxn * 40];


void maintain(ll id) {
	tree[id] = tree[ls[id]] + tree[rs[id]];
}

void update(ll posi, ll d, ll &id, ll left, ll right) {
	if (!id) id = ++cnt;
	if (left == right && left == posi) {
		tree[id] += d;
		return ;
	}
	ll mid = (left + right) >> 1;
	if (posi <= mid) update(posi, d, ls[id], left, mid);
	else 			 update(posi, d, rs[id], mid + 1, right);
	maintain(id);
}

ll query(ll L, ll R, ll id, ll left, ll right) {
	if (!id) return 0;
	if (L <= left && right <= R) return tree[id];
	ll mid = (left + right) >> 1;
	ll res = 0;
	if (L <= mid) res += query(L, R, ls[id], left, mid);
	if (R > mid)  res += query(L, R, rs[id], mid + 1, right);
	return res;
}

ll kth_element(ll k, ll id, ll left, ll right) {
	if (left == right) return left;
	ll mid = (left + right) >> 1;
	if (k <= tree[ls[id]]) return kth_element(k, ls[id], left, mid);
	else            	   return kth_element(k - tree[ls[id]], rs[id], mid + 1, right);
}

ll frank(ll x) {
	return query(MIN_X, x - 1, 1, MIN_X, MAX_X) + 1;
}

ll prev(ll x) {
	return kth_element(frank(x) - 1, 1, MIN_X, MAX_X);
}

ll suf(ll x) {
	return kth_element(frank(x + 1), 1, MIN_X, MAX_X);
}

void solve() {
	
	cin >> n;
	
	for (int i = 1; i <= n; i++) {
		int opt, x;
		cin >> opt >> x;
		if (opt == 1) update(x, 1, root, MIN_X, MAX_X);
		else if (opt == 2) update(x, -1, root, MIN_X, MAX_X);
		else if (opt == 3) cout << frank(x) << "\n";
		else if (opt == 4) cout << kth_element(x, 1, MIN_X, MAX_X) << "\n";
		else if (opt == 5) cout << prev(x) << "\n";
		else cout << suf(x) << "\n";
	}
	
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	//	freopen("T1.in", "r", stdin);
	//	freopen("T1.out", "w", stdout);
	
	int T = 1;
	//	cin >> T;
	while (T--) solve();
	
	return 0;
}

CF915E Physical Education Lessons

View Code
#include<bits/stdc++.h>
using namespace std;

using lf = double;
using ll = long long;
using ull = unsigned long long;

const int maxn = 3e5 + 5;
const int MIN_X = 0;
const int MAX_X = 1e9;

int n, q;
int cnt = 1, root = 1;
int a[maxn];
int tree[maxn  * 70];
int lazy_tag[maxn * 70];
int ls[maxn * 70], rs[maxn * 70];

void maintain(int id) {
	tree[id] = tree[ls[id]] + tree[rs[id]];
}

void addtag(int &id, int d, int left, int right) {
	if (!id) id = ++cnt;
	tree[id] = d * (right - left + 1);
	lazy_tag[id] = d;
}

void pushdown(int id, int left, int right) {
	if (lazy_tag[id]) {
		int mid = left + ((right - left) >> 1);
		addtag(ls[id], lazy_tag[id], left, mid);
		addtag(rs[id], lazy_tag[id], mid + 1, right);
		lazy_tag[id] = 0;
	}
}

void update(int L, int R, int d, int &id, int left, int right) {
	if (!id) id = ++cnt;
	if (L <= left && right <= R) {
//		tree[id] += d;
		addtag(id, d, left, right);
		return ;
	}
	pushdown(id, left, right);
	int mid = left + ((right - left) >> 1);
	if (L <= mid) update(L, R, d, ls[id], left, mid);
	if (R > mid)  update(L, R, d, rs[id], mid + 1, right);
	maintain(id);
}

int query(int L, int R, int id, int left, int right) {
	if (!id) return 0;
	if (L <= left && right <= R) return tree[id];
	pushdown(id, left, right);
	int mid = left + ((right - left) >> 1);
	int res = 0;
	if (L <= mid) res += query(L, R, ls[id], left, mid);
	if (R > mid)  res += query(L, R, rs[id], mid + 1, right);
	return res;
}

int kth_element(int k, int id, int left, int right) {
	if (left == right) return left;
	int mid = left + (right - left) >> 1;
	if (k <= tree[ls[id]]) return kth_element(k, ls[id], left, mid);
	else            	   return kth_element(k - tree[ls[id]], rs[id], mid + 1, right);
}

int frank(int x) {
	return query(MIN_X, x - 1, 1, MIN_X, MAX_X) + 1;
}

int prev(int x) {
	return kth_element(frank(x) - 1, 1, MIN_X, MAX_X);
}

int suf(int x) {
	return kth_element(frank(x + 1), 1, MIN_X, MAX_X);
}

void solve() {
	
	cin >> n >> q;
	
	update(1, n, 1, root, MIN_X, MAX_X);
	
	for (int i = 1; i <= q; i++) {
		int l, r, opt;
		cin >> l >> r >> opt;
		
		update(l, r, opt - 1, root, MIN_X, MAX_X);
		
		cout << query(1, n, 1, MIN_X, MAX_X) << "\n";
	}
	
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	//	freopen("T1.in", "r", stdin);
	//	freopen("T1.out", "w", stdout);
	
	int T = 1;
	//	cin >> T;
	while (T--) solve();
	
	return 0;
}

【模板】线段树 1.5

View Code
#include<bits/stdc++.h>
using namespace std;

using lf = double;
using ll = unsigned long long;
//using ull = long long;

const int maxn = 1e5 + 5;
const ll MIN_X = 1;
const ll MAX_X = 1e9;

int n, m;
ll cnt = 1, root = 1;
ll a[maxn];
ll tree[maxn * 70];
ll lazy_tag[maxn * 70];
ll ls[maxn * 70], rs[maxn * 70];

void maintain(ll id) {
	tree[id] = tree[ls[id]] + tree[rs[id]];
}

void addtag(ll &id, ll d, ll left, ll right) {
	if (!id) 
		id = ++cnt;
//		tree[id] = (left + right) * (right - left + 1) >> 1;
	
	tree[id] += (right - left + 1) * d;
	lazy_tag[id] += d;
}

void pushdown(ll id, ll left, ll right) {
	if (lazy_tag[id]) {
		ll mid = (left + right) >> 1;
		addtag(ls[id], lazy_tag[id], left, mid);
		addtag(rs[id], lazy_tag[id], mid + 1, right);
		lazy_tag[id] = 0;
	}
}

void update(ll L, ll R, ll d, ll &id, ll left, ll right) {
	if (!id) 
		id = ++cnt;
//		tree[id] = (left + right) * (right - left + 1) >> 1;
	
	if (L <= left && right <= R) {
		addtag(id, d, left, right);
		return ;
	}
	pushdown(id, left, right);
	ll mid = (left + right) >> 1;
	if (L <= mid) update(L, R, d, ls[id], left, mid);
	if (R > mid)  update(L, R, d, rs[id], mid + 1, right);
	maintain(id);
}

ll query(ll L, ll R, ll &id, ll left, ll right) {
	if (!id) 
		id = ++cnt;
//		tree[id] = (left + right) * (right - left + 1) >> 1;
	
	if (L <= left && right <= R) return tree[id];
	pushdown(id, left, right);
	ll mid = (left + right) >> 1;
	ll res = 0;
	if (L <= mid) res += query(L, R, ls[id], left, mid);
	if (R > mid)  res += query(L, R, rs[id], mid + 1, right);
	return res;
}

void solve() {

	cin >> n >> m;

	for (int i = 1; i <= m; i++) {
		int opt;
		cin >> opt;
		if (opt == 1) {
			ll l, r, k;
			cin >> l >> r >> k;
			update(l, r, k, root, 1, n);
		} else {
			ll l, r;
			cin >> l >> r;
			cout << ((l + r) * (r - l + 1) >> 1) + query(l, r, root, 1, n) << "\n";
		}
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);

	//	freopen("T1.in", "r", stdin);
	//	freopen("T1.out", "w", stdout);

	int T = 1;
	//	cin >> T;
	while (T--) solve();

	return 0;
}

B.周测

T1 hotel

组内链接

题目描述
奶牛们正北上加拿大雷湾进行文化之旅,准备在苏必利尔湖阳光明媚的湖畔享受假期。作为称职的旅行代理,贝茜将著名的坎伯兰街公牛麋鹿酒店定为它们的下榻之处。这座巨型酒店拥有N间客房(1 ≤ N ≤ 50,000),所有房间都位于一条极长走廊的同一侧(当然是为了更好地欣赏湖景)。

奶牛和其他旅客以Di(1 ≤ Di ≤ N)为团体规模抵达前台办理入住。每个团体i会向当值的麋鹿管理员坎穆申请Di间连续客房。若有可用连续房间,坎穆会分配房号r至r+Di-1的序列;若无可用连续房间,则会礼貌推荐其他住宿。坎穆总是尽可能选择最小的r值。

旅客也会以连续房间为单位退房。每次退房请求包含参数Xi和Di,表示腾空Xi至Xi+Di-1号房间(1 ≤ Xi ≤ N-Di+1)。这些房间在退房前可能部分或全部本就空置。

你的任务是协助坎穆处理M次(1 ≤ M < 50,000)入住/退房请求。酒店初始处于全空状态。

输入格式

  • 第1行:两个空格分隔的整数:NM
  • 第2..M+1行:第i+1行包含以下两种格式之一的请求:

(a) 两个空格分隔的整数表示入住请求:1和Di

(b) 三个空格分隔的整数表示退房请求:2、XiDi

输出格式

  • 第1...行:对每个入住请求,输出一行整数r表示分配到的连续房间起始房号。若无法满足请求,则输出0。

原题

View Code
#include<bits/stdc++.h>
using namespace std;

using lf = double;
using ll = long long;
using ull = unsigned long long;

const int maxn = 5e4 + 5;

struct node{
	ll l, r;
	ll sum;
};

int n, m;
node tree[maxn << 2];
ll lazy_tag[maxn << 2];

ll ls(ll id) {return id << 1;}
ll rs(ll id) {return id << 1 | 1;}

void maitain(ll id, ll left, ll right) {
	ll mid = (left + right) >> 1;
	tree[id].l = tree[ls(id)].l;
	if (tree[ls(id)].l == mid - left + 1)
		tree[id].l += tree[rs(id)].l;
	tree[id].r = tree[rs(id)].r;
	if (tree[rs(id)].r == right - mid)
		tree[id].r += tree[ls(id)].r;
	tree[id].sum = max({tree[ls(id)].sum, tree[rs(id)].sum, tree[ls(id)].r + tree[rs(id)].l});
}

void pushdown(ll id, ll left, ll right) {
	if (lazy_tag[id] != -1) {
		lazy_tag[ls(id)] = lazy_tag[rs(id)] = lazy_tag[id];
		if (lazy_tag[id] == 1) {
			tree[ls(id)].l = tree[ls(id)].r = tree[ls(id)].sum = 0;
			tree[rs(id)].l = tree[rs(id)].r = tree[rs(id)].sum = 0;
		} else {
			ll mid = (left + right) >> 1;
			tree[ls(id)].l = tree[ls(id)].r = tree[ls(id)].sum = mid - left + 1;
			tree[rs(id)].l = tree[rs(id)].r = tree[rs(id)].sum = right - mid;
		}
		lazy_tag[id] = -1;
	}
}

void build(ll id, ll left, ll right) {
	lazy_tag[id] = -1;
	if (left == right) return tree[id].l = tree[id].r = tree[id].sum = 1, void();
	
	ll mid = (left + right) >> 1;
	build(ls(id), left, mid);
	build(rs(id), mid + 1, right);
	maitain(id, left, right);
}

void upadate(ll L, ll R, ll d, ll id = 1, ll left = 1, ll right = n) {
	if (L <= left && right <= R) {
		ll what = 0;
		if (d == 0) what = right - left + 1;
		lazy_tag[id] = d;
		tree[id].l = tree[id].r = tree[id].sum = what;
		return ;
	}
	pushdown(id, left, right);
	ll mid = (left + right) >> 1;
	if (L <= mid) upadate(L, R, d, ls(id), left, mid);
	if (R > mid) upadate(L, R, d, rs(id), mid + 1, right);
	maitain(id, left, right);
}

ll query(ll d, ll id = 1, ll left = 1, ll right = n) {
	if (left == right) return left;
	ll mid = (left + right) >> 1;
	if (tree[ls(id)].sum >= d) return query(d, ls(id), left, mid);
	else if (tree[ls(id)].r + tree[rs(id)].l >= d) return mid - tree[ls(id)].r + 1;
	else return query(d, rs(id), mid + 1, right);
	return 0;
}

void solve() {
	cin >> n >> m;
	build(1, 1, n);
	for (int i = 1; i <= m; i++) {
		int opt;
		cin >> opt;
		
		if (opt == 1) {
			int d;
			cin >> d;
			if (d > tree[1].sum) {
				cout << "0\n";
				continue;
			}
			int r = query(d);
			cout << r << "\n";
			upadate(r, r + d - 1, 1);
		} else {
			int x, d;
			cin >> x >> d;
			upadate(x, x + d - 1, 0);
		}
		
	}
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
//	freopen("hotel.in", "r", stdin);
////	freopen("hotel1.in", "r", stdin);
//	freopen("hotel.out", "w", stdout);
	
	int T = 1;
//	cin >> T;
	while (T--) solve();

	return 0;
}

T2 Palindrome Query

哈哈哈,打的暴力比某些Hash得的分还要高

View BF Code
#include<bits/stdc++.h>
using namespace std;

using lf = double;
using ll = long long;
using ull = unsigned long long;

void solve() {

	int n, m;
	cin >> n >> m;
	string str;
	cin >> str;

	for (int i = 1; i <= m; i++){
		int opt;
		cin >> opt;
		if (opt == 1) {
			int x;
			char c;
			cin >> x >> c;
			str[x - 1] = c;
		} else {
			int l, r;
			cin >> l >> r;
			int i = l, j = r;
			bool flag = 1;
			if ((r - l + 1) & 1) {
				while (j - i > 1) {
					if (str[i - 1] != str[j - 1]) {
						cout << "No\n";
						flag = 0;
						break;
					}
					i++, j--;
				}
				if (flag) cout << "Yes\n";
			} else {
				while (i < j) {
					if (str[i - 1] != str[j - 1]) {
						cout << "No\n";
						flag = 0;
						break;
					}
					i++, j--;
				}
				if (flag) cout << "Yes\n";
			}
		}
	}
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);

//	freopen("palindrome.in", "r", stdin);
//	freopen("palindrome.out", "w", stdout);

	int T = 1;
	//	cin >> T;
	while (T--) solve();

	return 0;
}

思路:
一眼 Hash
听到有人双Hash 哈希冲突了
还有个单Hash 冲突的(模数用的1333331)

Segment Tree 的结构体设计:

struct node {
	long long L, R;
	long long len;
}

\(\rarr\) 结构体中 + 的实现:

node operator+(node a,node b){
	long long L=a.L*Pow[b.len]%mod+b.L;
	L%=mod;
	long long R=b.R*Pow[a.len]%mod+a.R;
	R%=mod;
	int len=a.len+b.len;
	return {L,R,len};
}

模数用 1e9 + 7 就好

代码:

View Code
#include<bits/stdc++.h>
using namespace std;

using lf = double;
using ll = long long;
using ull = unsigned long long;

const int maxn = 1e6 + 5;
ll ls(ll i) {
	return i << 1;
}
ll rs(ll i) {
	return i << 1 | 1;
}

const ll M = 1e9 + 7;

#define mod %

struct node{
	ll len;
	ll left_hash, right_hash;
	node(){}
	node(char c) {
		len = 1;
		left_hash = c - 'a' + 1;
		right_hash = c - 'a' + 1;
	}
	node(ll l, ll lh, ll rh) : len(l), left_hash(lh), right_hash(rh) {}
};

string str;
node tree[maxn << 2];
ll base = 131;
ll Pow[maxn];

node operator+(node a, node b) {
	node res;
	res.len = a.len + b.len;
	res.left_hash = (a.left_hash * Pow[b.len] mod M + b.left_hash) mod M;
	res.left_hash = (res.left_hash + M) mod M;
	res.right_hash = (b.right_hash * Pow[a.len] mod M + a.right_hash) mod M;
	res.right_hash = (res.right_hash + M) mod M;
	return res;
}

void maintain(ll id) { // push up
	tree[id] = tree[ls(id)] + tree[rs(id)];
}

void build(ll id, ll left_, ll right_) {
	if (left_ == right_) {
		tree[id] = node(str[left_]);
		return;
	}
	ll mid = (left_ + right_) >> 1;
	build(ls(id), left_, mid);
	build(rs(id), mid + 1, right_);
	maintain(id);
}

void update_range(ll posi, ll id, ll left_, ll right_, char d) {
	if (left_ == right_) {
		tree[id] = node(d);
		return;
	}
	ll mid = (left_ + right_) >> 1;
	if (posi <= mid) update_range(posi, ls(id), left_, mid, d);
	else if (posi > mid) update_range(posi, rs(id), mid + 1, right_, d);
	maintain(id);
}

node query_range(ll L, ll R, ll id, ll left_, ll right_) {
	if (L <= left_ && right_ <= R) return tree[id];
	ll mid = (left_ + right_) >> 1;
	node res(0, 0, 0);
	if (L <= mid) res = res + query_range(L, R, ls(id), left_, mid);
	if (R > mid) res = res + query_range(L, R, rs(id), mid + 1, right_);
	return res;
}

node query(ll L, ll R, int n) {
	return query_range(L, R, 1, 1, n);
}

void update(ll posi, char d, int n) {
	update_range(posi, 1, 1, n, d);
}


void solve() {
	int n, m;
	cin >> n >> m;
	
	cin >> str;
	
	str = " " + str;
	
	Pow[0] = 1;
	for (int i = 1; i <= maxn - 5; i++)
		Pow[i] = (Pow[i - 1] * base mod M + M) mod M;
	
	build(1, 1, n);
	
	while (m--) {
		int oper;
		cin >> oper;
		
		if (oper == 1) {
			int x;
			char c;
			cin >> x >> c;
			update(x, c, n);
			str[x] = c;
		} else {
			int l, r;
			cin >> l >> r;
			node res = query(l, r, n);
			res.left_hash = (res.left_hash + M) mod M;
			res.right_hash = (res.right_hash + M) mod M;
			if (res.left_hash == res.right_hash) cout << "Yes\n";
			else								 cout << "No\n";
			
//			for (int i = l; i <= r; i++) cout << str[i];
//			cout << "\n";
//			
//			cout << res.left_hash << " " << res.right_hash << "\n";
		}
	}
	
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	int T = 1;
	while (T--) solve();
	
	return 0;
}

T3 回转寿司

权值线段树+动态开点

对于每一个 sum[i] ,查询 [sum[i] - r, sum[i] - l] 内的值得个数

View Code
#include <bits/stdc++.h>
using namespace std;

using lf = double;
using ll = long long;
using ull = unsigned long long;

const int maxn = 1e5 + 5;
const ll MIN_X = -1e10;
const ll MAX_X = 1e10;

int n;
ll cnt = 1, root = 1;
ll a[maxn];
ll tree[maxn * 70];
ll ls[maxn * 70], rs[maxn * 70];

void maintain(ll id) { tree[id] = tree[ls[id]] + tree[rs[id]]; }

void update(ll posi, ll d, ll &id, ll left = MIN_X, ll right = MAX_X) {
	if (!id)
		id = ++cnt;
	if (left == right && left == posi) {
		tree[id] += d;
		return;
	}
	ll mid = (left + right) >> 1;
	if (posi <= mid)
		update(posi, d, ls[id], left, mid);
	else
		update(posi, d, rs[id], mid + 1, right);
	maintain(id);
}

ll query(ll L, ll R, ll &id, ll left = MIN_X, ll right = MAX_X) {
	if (!id)
		id = ++cnt;
	if (L <= left && right <= R)
		return tree[id];
	ll mid = (left + right) >> 1;
	ll res = 0;
	if (L <= mid)
		res += query(L, R, ls[id], left, mid);
	if (R > mid)
		res += query(L, R, rs[id], mid + 1, right);
	return res;
}

void solve() {
	
	int n, l, r;
	cin >> n >> l >> r;
	
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		a[i] = a[i - 1] + x;
	}
	
	update(0, 1, root);
	
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += query(a[i] - r, a[i] - l, root);
		update(a[i], 1, root);
	}
	
	cout << ans << "\n";
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	//	freopen("T1.in", "r", stdin);
	//	freopen("T1.out", "w", stdout);
	
	int T = 1;
	//	cin >> T;
	while (T--) solve();
	
	return 0;
}
posted @ 2025-12-13 10:35  Yangyihao  阅读(3)  评论(1)    收藏  举报