NOIAC 一些题目

白嫖了十场比赛,偶尔看看,大概都是口胡

Contest05

A

给定 \(n\) 个点,\(m\) 条边的无向简单图,你需要判断,这个图的补图的点是否可以分成两个集合,使这两个集合之间无边相连。

我们考虑找到度数最小的点,那么这个点和没有和他相邻的点一定是一个集合,我们可以考虑缩一下,那么剩下来的点数量是 \(O(\sqrt n)\) 的,

之后考虑暴力,如果两个点之间有边,那么就合并这两个点,最后看看连通分量的个数即可。

B

\[\sum_{i=1}^N\lfloor \frac{M}{i} \rfloor (\mu^7d^3\epsilon^{11})(i) \]

这玩意,首先明显就是

\[\sum_{i=1}^N\lfloor \frac{M}{i} \rfloor \mu(i) \]

然后大概直接做就完事了。

C

明天更

好吧上边的鸽子了

Contest 6

A

过于简单了咕咕咕

B

有两个长为 \(n\) 的排列 \(A,B\),你可以执行以下操作:选择排列 \(A\) 一个区间 \([l,r]\),并将区间里的数按升序或降序排序。你需要再 \(m\) 步操作内使 \(\forall i\in [1,n]A_i=B_i\)

我们考虑首先把 \(A\) 做一次排序,那么还剩下 \(n-1\) 次操作,我们用这 \(n-1\) 次操作每次确定一个位置即可。

我们从左到右采取这个策略就行,假定再 \(A\)\(pos\) 位置的数字才是 \(B_i\),那么我们直接将 \(A[i:pos]\) 升序或者降序排列, \(A_{pos}\) 一定可以跑到 \(i\) 位置上,因为这样操作满足有一个性质就是 \(A_{i}\) 一定是 \(A[1:i]\) 的最大值或者是最小值

C

NOI.AC

一道巨大码农题,老讨厌这种东西了。

我们首先特判一些 \(ATK=0\) 的情况。然后考虑对于那些攻击力可以翻倍但是生命值减半的怪物,暴力做 \(\log V\) 次,然后就剩下攻击力不翻倍的怪物了,这样就可以处理出来这些怪物死亡的时间暴力去用线段树模拟机可了。

D

巨大题目。

我们考虑用直接用可并堆暴力做就完事了,咋整啊,就你考虑每个节点用俩堆维护每个黑色到他的权值和每个白色到她的权值。那么每次直接往父节点合并就好了。记得打标记。

然后在这个节点上我们可以考虑做决策就是说,取出来最大的黑和最大的白拼在一起,直到不优秀为止。

发现这个做法肯定不太行,考虑可悔贪心,每次把取出来的黑色的权值相反数放到白色里,白色相反数放到黑色里。

Contest7

题面:https://files.cnblogs.com/files/blogs/575626/Contest8_by_NOIAC.zip

这场有点送温暖,感觉很不错

A

我们考虑,什么时候会出现问题,就比如说,如果对于同一个颜色的东西数量为 \(num\),如果 \(num\times k>n\),则肯定不合法。

否则一定合法,我们随便放应该就行了。就比如说策略首先决定多少组,应该有一些是 \(k+1\) 的,还有一些 \(k\) 的。我们只要按照每个颜色的数量升序或者降序排就好了吧。

#include <bits/stdc++.h>
const int N = 1e5 + 10;
int n, k, a[N];
int main() {
	std::ios::sync_with_stdio(0);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin >> n >> k;
	for (int i = 1; i <= n; ++i) {
		std::cin >> a[i];
	}
	for (int i = 1; i <= n; ++i) {
		int j = i;
		while (j < n && a[j + 1] == a[i]) {
			++j;
		}
		if (k * (j - i + 1LL) > n) {
			std::cout << "F\n";
			return 0;
		}
		i = j;
	}
	std::cout << "T\n";
	return 0;
}

B

这个题目的意思十分不明确,

就是考虑,如果这一维度只有一个,那么肯定每个方块都被刷了两个面,否则应该是有 \(2\) 个被刷一面的,还有 \((x-2)\) 个没被刷的。

#include <bits/stdc++.h>
const int N = 1e4 + 10;
int n;
unsigned long long a[N * 2];
int main() {
	std::ios::sync_with_stdio(0);
	std::cin.tie(0);
	std::cout.tie(0);
	a[0] = 1;
	std::cin >> n;
	unsigned long long x;
	for (int i = 1; i <= n; ++i) {
		std::cin >> x;
		if (x == 1) {
			for (int j = i * 2; j >= 2; --j) {
				a[j] = a[j - 2];
			}
			a[0] = a[1] = 0;
		}
		else {
			for (int j = i * 2; j >= 1; --j) {
				a[j] = a[j - 1] * 2 + (x - 2) * a[j];
			}
			a[0] = (x - 2) * a[0];
		}
	}
	for (int i = 2 * n; ~i; --i) {
		std::cout << a[i] << " \n"[!i];
	}
	return 0;
}

C

答案求的是

\[\sum_{i=1}^n\max(|x_i-x|,|y_i-y|) \]

我们考虑 \(|a-b|+|a+b|=2\max(|a|,|b|)\),那么只要让 \(S=x_i-y_i,T=x_i+y_i\),我们只要求

\[\sum_{i=1}^n|S_i-x|+|T_i-y| \]

这玩意显然很好求,然后除以二就是了。

#include <bits/stdc++.h>
using std::cin;
using std::cout;
const int N = 1e6 + 10;
int n, q, x[N], y[N];
long long prex[N], prey[N], sufx[N], sufy[N];
int main() {
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> q;
	for (int i = 1, t1, t2; i <= n; ++i) {
		cin >> t1 >> t2;
		x[i] = t1 + t2;
		y[i] = t1 - t2;
	}
	std::sort(x + 1, x + 1 + n);
	std::sort(y + 1, y + 1 + n);
	for (int i = 1; i <= n; ++i) {
		prex[i] = prex[i - 1] + x[i];
		prey[i] = prey[i - 1] + y[i];
	}
	for (int i = n; i; --i) {
		sufx[i] = sufx[i + 1] + x[i];
		sufy[i] = sufy[i + 1] + y[i];
	}
	long long X, Y;
	for (int i = 1, t1, t2; i <= q; ++i) {
		cin >> t1 >> t2;
		X = t1 + t2;
		Y = t1 - t2;
		int posx = std::lower_bound(x + 1, x + 1 + n, X) - x, posy = std::lower_bound(y + 1, y + 1 + n, Y) - y;
		cout << (X * (2 * posx - 2 - n) - prex[posx - 1] + sufx[posx] + Y * (2 * posy - 2 - n) - prey[posy - 1] + sufy[posy]) / 2 << '\n';
	}
	return 0;
}

D

我们首先不考虑第二个限制,那么答案 \(f(n)\) 应该是用插板法做一下,那么是 \(\binom{n+k-1}{k}\)

这玩意我们考虑就是说,我们在考虑,对于 \(\gcd\)\(a\) 的倍数的情况下的种数是 \(f(n/a)\)

那么我们考虑一个叫做莫比乌斯反演的东西,我们就可以得到,\(g(n)=\sum_{i=1}^n\mu(i)f(i)\),然后直接杜教筛。

#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::map;
const int mod = 998244353, N = 1e7 + 10;
int ksm(int x, int y) {
	int res = 1;
	for (; y; y >>= 1, x = 1LL * x * x % mod) {
		if (y & 1) {
			res = 1LL * res * x % mod;
		}
	}
	return res;
}
inline int Mod(int x) {
	if (x >= mod) {
		return x - mod;
	}
	else if (x < 0) {
		return x + mod;
	}
	else {
		return x;
	}
}
bool mark[N];
int k, pri[N / 15], mu[N], fac[N], ifac[N];
map<long long, int> mp;
long long n, ans;
inline int calc(long long n) {
	if (n + k - 1 < N) {
		return 1LL * fac[n + k - 1] * ifac[n - 1] % mod;
	}
	else {
		int res = 1;
		for (long long i = n; i < n + k; ++i) {
			res = 1LL * res * (i % mod) % mod;
		}
		return res;
	}
}
int getmu(long long n) {
	if (n < N) {
		return mu[n];
	}
	if (mp.count(n)) {
		return mp[n];
	}
	int ans = 1;
	for (long long l = 2, r; l <= n; l = r + 1) {
		r = n / (n / l);
		ans = Mod(ans - (r - l + 1) % mod * getmu(n / l) % mod);
	}
	return mp[n] = ans;
}
int main() {
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	mu[1] = 1;
	for (int i = 2; i < N; ++i) {
		if (!mark[i]) {
			pri[++pri[0]] = i;
			mu[i] = -1;
		}
		for (int j = 1; i * pri[j]  < N && j <= pri[0]; ++j) {
			if (i % pri[j]) {
				mark[i * pri[j]] = 1;
				mu[i * pri[j]] = -mu[i];
			}
			else {
				mark[i * pri[j]] = 1;
				break;
			}
		}
		mu[i] = Mod(mu[i - 1] + mu[i]);
	}
	fac[0] = 1;
	for (int i = 1; i < N; ++i) {
		fac[i] = 1LL * i * fac[i - 1] % mod;
	}
	ifac[N - 1] = ksm(fac[N - 1], mod - 2);
	for (int i = N - 2; ~i; --i) {
		ifac[i] = (i + 1LL) * ifac[i + 1] % mod;
	}
	cin >> n >> k;
	int lst, nxt = 0;
	for (long long l = 1, r; l <= n; l = r + 1) {
		r = n / (n / l);
		lst = nxt;
		nxt = getmu(r);
		ans = Mod(ans + 1LL * calc(n / l) * Mod(nxt - lst) % mod);
	}
	int ret = 1;
	for (int i = 1; i <= k; ++i) {
		ret = 1LL * ret * i % mod;
	}
//	cout << ans << '\n';
	ans = 1LL * ans * ksm(ret, mod - 2) % mod;
	cout << ans << '\n';
	return 0;
}

Contest10

题面:https://files.cnblogs.com/files/blogs/575626/statement.zip

A

我们考虑转化这个题目,就是求一个长度为 \(k+1\) 的序列 \(a\),满足 \(\sum a = n + 1\),求 \(\sum_{i\&1 = 1} a_i \times \sum_{i \&1 = 0} a_i\) 最大化。

那么我们只要一半一半分即可,要注意的是,特判 \(k=0\) 的情况。

#include <bits/stdc++.h>
const int N = 1e5 + 10;
int n, k, a[N];
int main() {
	std::cin >> n >> k;
	if (k == 0) {
		std::cout << "0\n";
		for (int i = 1; i <= n; ++i) {
			std::cout << "0 ";
		}
		return 0;
	}
	int _2 = (n + 1) / 2, _1 = (n + 2) / 2;
	std::cout << 1LL * _1 * _2 << '\n';
	for (int i = k + 1; i >= 3; --i) {
		a[i] = 1;
		if (i & 1) {
			_1--;
		}
		else {
			_2--;
		}
	}
	a[1] = _1;
	a[2] = _2;
	for (int i = 1; i <= k + 1; ++i) {
		for (int j = 1; j < a[i]; ++j) {
			std::cout << "0 ";
		}
		if (i != k + 1) {
			std::cout << "1 ";
		}
	}
	std::cout << '\n';
	return 0;
}

B

我们考虑对于每一条长度为 \(k\) 的链,它对于答案的贡献是 \(\sum_{i=0}^k \binom{k}{i}i^2\)

我们考虑化简这个式子,由于 \((1+x)^n=\sum_{i=0}^n\binom{n}{i} x^i\),对于这个式子求导,有 \(n(1+x)^{n-1}=\sum_{i=1}^n\binom{n}{i} ix^{i-1}\) ,两边同乘上 \(x\),有 \(nx(1+x)^{n-1}=\sum_{i=1}^n\binom{n}{i}x^i\),然后再求导即可。

那么我们得到了,一条长度为 \(k\) 的链的贡献其实是 \(k(k+1)2^{n-2}\) 的。那么我们考虑只要求 \((k+1)k\) 就可以了。这玩意可以通过维护一个子树节点里到这个子树的根的长度的二次方和,一次方和和零次方和来完成。

具体可以看代码。要注意的是,像这种n=3e6的题目,尽量用邻接表。

#include <bits/stdc++.h>
const int N = 3e6 + 10, mod = 998244353, inv2 = (mod + 1) / 2;
using std::cin;
using std::cout;
int n, h[N], cnt;
struct Edge {
	int lac, to;
	void insert(int x, int y) {
		lac = h[x];
		h[x] = cnt++;
		to = y;
	}
} e[N * 2];
long long ans, sum1[N], sum2[N], sum3[N];
void dfs(int u, int f) {
	sum1[u] = sum2[u] = sum3[u] = 1;
	ans = (ans + 2) % mod;
	for (int i = h[u]; ~i; i = e[i].lac) {
		int j = e[i].to;
		if (j != f) {
			dfs(j, u);
			ans = (ans + sum1[u] * sum3[j] + sum1[j] * sum3[u] + 2 * sum2[u] * sum2[j] % mod + sum2[u] * sum3[j] + sum2[j] * sum3[u]) % mod;
			sum1[u] = (sum1[u] + sum1[j] + 2 * sum2[j] + sum3[j]) % mod;
			sum2[u] = (sum2[u] + sum2[j] + sum3[j]) % mod;
			sum3[u] = sum3[u] + sum3[j];
		}
	}
}
int main() {
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	if (n == 1) {
		cout << '1';
		return 0;
	}
	memset(h, -1, sizeof h);
	for (int i = 1, x, y; i < n; ++i) {
		cin >> x >> y;
		e[cnt].insert(x, y);
		e[cnt].insert(y, x);
	}
	dfs(1, 0);
	for (int i = 1; i <= n - 2; ++i) {
		ans = ans * 2 % mod;
	}
	cout << ans << '\n';
	return 0;
}

C

既然题目中的区间都是没有相交关系,那么必定是一棵树的关系,一个区间被称为是另一个区间的父亲,当且仅当它是包含后边这个区间的最小区间。我们可以对于所有位置维护一个他们现在属于什么区间,然后我们发现,我们每次只是暴力修改一个位置,那么我们修改这个位置的时候,看看这个区间是否合法了,如果合法了,那么这个区间所属于的位置全部给他父亲即可。

关于前驱可以用 set 维护,而判断一个区间是否合法,我们令 \(pre_i\) 表示 \(a_i\) 上次出现的位置(没有则为 \(0\)),那么一个区间合法,当且仅当 \(\max_{i=l}^rpre_i < l\)

#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::set;
using std::pair;
using std::vector;
using std::tuple;
using std::get;
template<typename T>
inline T max(const T &x, const T &y) {
	return x > y ? x : y;
}
const int N = 5e5 + 10;
int n, m, q, a[N], x[N], y[N], fa[N], id[N], L[N], lst[N], pre[N];
tuple<int, int, int> l[N];
set<int> rd, col_pos[N];
bool cmp(const tuple<int, int, int> &a, const tuple<int, int, int> &b) {
	if (get<0>(a) != get<0>(b)) {
		return get<0>(a) < get<0>(b);
	}
	else if (get<1>(a) != get<1>(b)) {
		return get<1>(a) > get<1>(b);
	}
	else {
		return get<2>(a) < get<2>(b);
	}
}
inline int ls(int k) {
	return k << 1;
}
inline int rs(int k) {
	return k << 1 | 1;
}
namespace SGT_col {
	int cov[N * 4];
	inline void down(int k) {
		if (cov[k]) {
			cov[ls(k)] = cov[k];
			cov[rs(k)] = cov[k];
			cov[k] = 0;
		}
	}
	void modify(int k, int l, int r, int ql, int qr, int x) {
		if (ql <= l && r <= qr) {
			cov[k] = x;
			return;
		}
		down(k);
		int mid = (l + r) / 2;
		if (ql <= mid) {
			modify(ls(k), l, mid, ql, qr, x);
		}
		if (mid < qr) {
			modify(rs(k), mid + 1, r, ql, qr, x);
		}
		return;
	}
	int query(int k, int pos, int l, int r) {
		if (l == r) {
			return cov[k];
		}
		down(k);
		int mid = (l + r) / 2;
		return pos <= mid ? query(ls(k), pos, l, mid) : query(rs(k), pos, mid + 1, r);
	}
}
namespace SGT_pre {
	int mx[N * 4];
	inline void up(int k) {
		mx[k] = max(mx[ls(k)], mx[rs(k)]);
	}
	void modify(int k, int l, int r, int pos, int x) {
		if (l == r) {
			mx[k] = x;
			return;
		}
		int mid = (l + r) / 2;
		pos <= mid ? modify(ls(k), l, mid, pos, x) : modify(rs(k), mid + 1, r, pos, x);
		up(k);
	}
	int query(int k, int l, int r, int ql, int qr) {
		if (ql <= l && r <= qr) {
			return mx[k];
		}
		int mid = (l + r) / 2, ret = 0;
		if (ql <= mid) {
			ret = max(ret, query(ls(k), l, mid, ql, qr));
		}
		if (mid < qr) {
			ret = max(ret, query(rs(k), mid + 1, r, ql, qr));
		}
		return ret;
	}
	void build(int k, int l, int r) {
		if (l == r) {
			mx[k] = pre[l];
			return;
		}
		int mid = (l + r) / 2;
		build(ls(k), l, mid);
		build(rs(k), mid + 1, r);
		up(k);
	}
}
int main() {
	cin.tie(0);
	cout.tie(0);
	std::ios::sync_with_stdio(0);
	cin >> n >> m >> q;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; ++i) {
		cin >> get<0>(l[i]) >> get<1>(l[i]);
		get<2>(l[i]) = i;
	}
	for (int i = 1; i <= q; ++i) {
		cin >> x[i] >> y[i];
	}
	for (int i = 1; i <= n; ++i) {
		col_pos[i].insert(0);
	}
	std::sort(l + 1, l + m + 1, cmp);
	for (int i = 1; i <= m; ++i) {
		auto it = rd.lower_bound(get<1>(l[i]));
		if (it != rd.end()) {
			fa[i] = id[*it];
		}
		rd.insert(get<1>(l[i]));
		id[get<1>(l[i])] = i;
	}
	for (int i = 1; i <= m; ++i) {
		SGT_col::modify(1, 1, n, get<0>(l[i]), get<1>(l[i]), i);
	}
	for (int i = 1; i <= n; ++i) {
		pre[i] = lst[a[i]];
		col_pos[a[i]].insert(i);
		lst[a[i]] = i;
	}
	SGT_pre::build(1, 1, n);
	memset(L, -1, sizeof L);
	for (int i = m; i; --i) {
		if (SGT_pre::query(1, 1, n, get<0>(l[i]), get<1>(l[i])) < get<0>(l[i])) {
			L[get<2>(l[i])] = 0;
			SGT_col::modify(1, 1, n, get<0>(l[i]), get<1>(l[i]), fa[i]);
		}
	}
	for (int i = 1; i <= q; ++i) {
		if (y[i] != a[x[i]]) {
			col_pos[a[x[i]]].erase(x[i]);
			auto it = col_pos[a[x[i]]].upper_bound(x[i]);
			if (it != col_pos[a[x[i]]].end()) {
				SGT_pre::modify(1, 1, n, *it, pre[*it] = *std::prev(it));
			}
			it = col_pos[y[i]].upper_bound(x[i]);
			if (it != col_pos[y[i]].end()) {
				SGT_pre::modify(1, 1, n, *it, pre[*it] = x[i]);
			}
			it = col_pos[y[i]].insert(x[i]).first;
			SGT_pre::modify(1, 1, n, x[i], pre[x[i]] = *std::prev(it));
			a[x[i]] = y[i];
		}
		int flag = 1;
		while (flag) {
			int now = SGT_col::query(1, x[i], 1, n);
			if (SGT_pre::query(1, 1, n, get<0>(l[now]), get<1>(l[now])) < get<0>(l[now])) {
				L[get<2>(l[now])] = i;
				SGT_col::modify(1, 1, n, get<0>(l[now]), get<1>(l[now]), fa[now]);
				flag = 1;
			}
			else {
				flag = 0;
			}
		}
	}
	int ans = 0;
	for (int i = 1; i <= m; ++i) {
		if (~L[i]) {
			ans ^= L[i];
		}
		else {
			ans ^= m + i;
		}
	}
	cout << ans << '\n';
	return 0;
}

D

这道题状态定义非常给力,我们令 \(f_{x,i}\) 表示当前为 \(x\) 节点,然后子树外给他 \(i\) 的加法的把子树都搞合法的最小花费。

那么

\[f_{x,i}=f_{rs,i}+f_{ls,i}+w(x,i) \]

其中 \(w(x,i)\) 表示 \(x\) 节点在外界给他 \(i\) 的加法之后自己要不要做 \(1\) 操作。

并且

\[f_{x,i}=\min(\min f_{x,j}+1,f_{x,i}) \]

直接 \(dp\) 有三十分。

我们考虑到这dp实际上是有段数的,于是可以线段树合并,那么复杂度就是 \(O(n\log ^ 2n)\) 的了。

这个 \(dp\) 如果用线段树合并做的话,非常考验维护标记的技巧了。

#include <bits/stdc++.h>
using std::cin;
using std::cout;
const int N = 2e5 + 10;
int n, K, tot, a[N], b[N], ls[N], rs[N], rt[N];
template<typename T>
inline T min(const T &x, const T &y) {
	return x > y ? y : x;
}
struct Node {
	int l, r, mn, tag, s;
} nd[N * 100];
void puttag(int k, int v) {
	if (!k) {
		return;
	}
	if (nd[k].mn >= v) {
		nd[k].mn = nd[k].s = v;
		nd[k].tag = nd[k].l = nd[k].r = 0;
	}
	else {
		nd[k].tag = !nd[k].tag ? v : min(v, nd[k].tag);
	}
}
void down(int k) {
	if (!nd[k].tag) {
		return;
	}
	puttag(nd[k].l, nd[k].tag - nd[k].s);
	puttag(nd[k].r, nd[k].tag - nd[k].s);
	nd[k].tag = 0;
}
void maintain(int k) {
	nd[k].mn = min(nd[nd[k].l].mn, nd[nd[k].r].mn) + nd[k].s;
}
void modify(int &k, int l, int r, int ql, int qr) {
	if (!k) {
		k = ++tot;
	}
	down(k);
	if (ql <= l && r <= qr) {
		nd[k].s++;
		nd[k].mn++;
		return;
	}
	int mid = (l + r) / 2;
	if (ql <= mid) {
		modify(nd[k].l, l, mid, ql, qr);
	}
	if (mid < qr) {
		modify(nd[k].r, mid + 1, r, ql, qr);
	}
	return maintain(k);
}
int merge(int x, int y) {
	if (!x || !y) {
		return x | y;
	}
	down(x);
	down(y);
	nd[x].l = merge(nd[x].l, nd[y].l);
	nd[x].r = merge(nd[x].r, nd[y].r);
	nd[x].s += nd[y].s;
	maintain(x);
	return x;
}
int query(int k, int l, int r) {
	if (!k) {
		return 0;
	}
	down(k);
	if (l == r) {
		return nd[k].s;
	}
	int mid = (l + r) / 2;
	return query(nd[k].l, l, mid) + nd[k].s;
}
int main() {
	std::ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> K;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i] >> b[i] >> ls[i] >> rs[i];
	}
	std::function<void(int)> dfs = [&] (int u) {
		if (ls[u]) {
			dfs(ls[u]);
		}
		if (rs[u]) {
			dfs(rs[u]);
		}
		rt[u] = merge(rt[ls[u]], rt[rs[u]]);
		if (a[u] > b[u]) {
			modify(rt[u], 0, K - 1, K - a[u], K - b[u] - 1);
		}
		if (a[u] < b[u]) {
			modify(rt[u], 0, K - 1, 0, K - b[u] - 1);
			modify(rt[u], 0, K - 1, K - a[u], K - 1);
		}
		puttag(rt[u], nd[rt[u]].mn + 1);
	};
	dfs(1);
	cout << query(rt[1], 0, K - 1) << '\n';
	return 0;
}
posted @ 2021-10-30 23:01  siriehn_nx  阅读(128)  评论(0编辑  收藏  举报