• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
The blog of EzSun
lyy && ?
博客园    首页    新随笔    联系   管理    订阅  订阅

若干贪心总结

对于贪心题,有一些通用的 \(trick\)。

  1. 分析题目中每个行为的贡献,我们要尽量使每一步的贡献最大/小。
  2. 当直接分析效果不好时,我们可以以小见大。如p11830,先分析每一个数的贡献,再去考虑优化方法。
  3. 有些题目是出自一些比较经典的模型,可以看这里
  4. 有很大一部分题目是关于最佳操作顺序的,我们可以任意找两个元素通过推式子的方式去分析,进而得出排序规则。
  5. 注意有些题目需要分类讨论。

特别的,对于反贪:
反贪主要有两种形式,一种是边做操作边反贪,一种是做完所有操作后再进行退款。实际做题时,要对于两种形式都分析一下,选取更合适的方式去做。

P11830 [省选联考 2025] 幸运数字

题解:

对于每一个数\(w\),
我们考虑这个数如何成为中位数。
设所有数中,有 \(a\) 个数\(< w\),\(b\) 个数 \(=w\),\(c\) 个数 $ > w$。
很容易发现,我们要使 \(b\) 尽量大。
所以,对于所有 \(l_{i,2} < w < r_{i,2}\) 的区间,都要选择放 \(r_{i,1}\) 个 \(w\)。
\(b\) 的值就确定下来了。
接下来考虑 \(a\) 和 \(c\)。
受到总个数的限制,我们设 \(a \in [l,r]\),\(c \in [L,R]\)。
不难发现,如果 \(w\) 是中位数,那么 \(c \leq a+b\) 且 \(c+b>a\)。
联立得 \(c \in [a-b+1,a+b]\)
将其与 \(a\) 与 \(c\) 原本的取值范围联立,得\(c \in [\max(L,l-b+1),\min(R,r+b)]\)。
那么只要 \(w\) 是否存在于任何一个区间中且 \(c\) 的取值范围不为空,\(w\) 便可以成为中位数。
\(O(V)\) 枚举 \(w\),\(O(N)\) 处理 \(c\) 的值域,我们便得到了一个 \(O(VN)\) 的算法。
考虑优化,我们考虑每一个区间,不难发现每一个区间都会为 \([l_{i,2},r_{i,2}]\) 的 \(w\) 提供 \(r_{i,1}\) 的贡献,为 \((r_{i,2},\infty)\) 的 \(l\) 提供 \(l_{i,1}\) 的贡献,为 \((r_{i,2},\infty)\) 的 \(r\) 提供 \(r_{i,1}\) 的贡献,为 \([1,l_{i,2})\) 的 \(L\) 提供 \(l_{i,1}\) 的贡献,为 \([1,l_{i,2})\) 的 \(R\) 提供 \(r_{i,1}\) 的贡献。
我们可以将 \(l_{i,2}\) 和 \(r_{i,2}\) 全部离散化存下来,再用差分处理每个区间的贡献,最后用前缀和汇总。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 7;
int CC, Task;
int n;
ll ls[N << 1], tot;
ll l1[N], r1[N], l2[N], r2[N];
ll b[N << 1], l[N << 1], r[N << 1], L[N << 1], R[N << 1], num[N << 1];
void init(){
	for(int i = 1; i <= n; i++){
		l1[i] = r1[i] = l2[i] = r2[i] = 0;
	}
	for(int i = 1; i <= (n << 1); i++){
		ls[i] = b[i] = l[i] = r[i] = L[i] = R[i] = num[i] = 0;
	}
	tot = 0;
}
void Main(){
	cin >> n;
	init();
	for(int i = 1; i <= n; i++){
		cin >> l1[i] >> r1[i] >> l2[i] >> r2[i];
		ls[++tot] = l2[i];
		ls[++tot] = r2[i] + 1;
	}
	sort(ls + 1, ls + (n << 1) + 1);
	int m = unique(ls + 1, ls + (n << 1) + 1) - ls - 1;
	for(int i = 1; i <= n; i++){
		int x = lower_bound(ls + 1, ls + m + 1, l2[i]) - ls, y = lower_bound(ls + 1, ls + m + 1, r2[i] + 1) - ls;
		b[x] += r1[i], b[y] -= r1[i];
		l[y] += l1[i], r[y] += r1[i];
		L[1] += l1[i], L[x] -= l1[i];
		R[1] += r1[i], R[x] -= r1[i];
		num[x]++;
		num[y]--;
	}
	ll ans = 0;
	for(int i = 1; i < m; i++){
		b[i] += b[i - 1];
		l[i] += l[i - 1];
		r[i] += r[i - 1];
		L[i] += L[i - 1];
		R[i] += R[i - 1];
		num[i] += num[i - 1];
		if(num[i] && max(l[i] - b[i] + 1, L[i]) <= min(r[i] + b[i], R[i])){
			ans += ls[i + 1] - ls[i];
		} 
	}
	printf("%lld\n", ans);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> CC >> Task;
	while(Task--){
		Main();
	}
	return 0;
}

P2168 [NOI2015] 荷马史诗

题解:

不难发现,这一题其实本质就是构造一棵 \(k\) 叉的哈夫曼树。
按照贪心的思想,构造过程其实就是每次选取权值最小的 \(k\) 个节点,并将其合并在一个子树下。
但这样合并有一个问题,若 \((n-1)mod(k-1)!=0\),最后合并到根节点的时候根节点的儿子会不足 \(k\) 个,导致无法实现最优。
而解决方法也很简单,加入一些权值为0的节点就可以了。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
const ll inf = 1e18;
int n, k;
ll w[N];
struct node{
	ll w, h;
	bool operator<(const node &a)const{
		if(w != a.w) return w > a.w;	
		else return h > a.h;
	}
};
priority_queue<node>q;
bool cmp1(ll x, ll y){
	return x > y;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> k;
	for(int i = 1; i <= n; i++){
		cin >> w[i];
		q.push({w[i], 1});
	}
	while((n - 1) % (k - 1) != 0){
		q.push({0, 1});
		n++;
	}
	ll ans = 0;
	while(q.size() >= k){
		ll sum = 0, hh = -1;
		for(int i = 1; i <= k; i++){
			sum += q.top().w;
			hh = max(hh, q.top().h);
			q.pop();
		}
		ans += sum;
		q.push({sum, hh + 1});
	}
	printf("%lld\n%lld\n", ans, q.top().h - 1);
	return 0;
}

P2751 [IOI 1996 / USACO4.2] 工序安排 Job Processing

题解:

注意点:是所有零件做完A步骤后才会开始做B步骤。
我们可以先将A步骤和B步骤看作两个独立的子问题处理。
不难发现,若是希望总和的最大值最小,一定是要A的正数第 \(i\) 配上B的倒数第 \(i\) 个。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 7, M = 37;
int n, m1, m2;
int a[M], b[M];
int ans1[N], ans2[N];
struct machine{
	int h, t;
	bool operator>(const machine &a)const{
		if(h + t != a.h + a.t) return h + t > a.h + a.t;
		else return h > a.h;
	}
};
priority_queue<machine, vector<machine>, greater<machine> >q;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m1 >> m2;
	for(int i = 1; i <= m1; i++){
		cin >> a[i];
	}
	for(int i = 1; i <= m2; i++){
		cin >> b[i];
	}
	for(int i = 1; i <= m1; i++){
		q.push({a[i], 0});
	}
	for(int i = 1; i <= n; i++){
		machine t = q.top();
		q.pop();
		q.push({t.h, t.t + t.h});
		ans1[i] = t.t + t.h;
	}
	sort(ans1 + 1, ans1 + n + 1);
	printf("%d ", ans1[n]);
	while(!q.empty()) q.pop();
	for(int i = 1; i <= m2; i++){
		q.push({b[i], 0});
	}
	for(int i = 1; i <= n; i++){
		machine t = q.top();
		q.pop();
		q.push({t.h, t.t + t.h});
		ans2[i] = t.t + t.h;
	}
	int s = 0;
	sort(ans2 + 1, ans2 + n + 1);
	for(int i = 1; i <= n; i++){
		s = max(s, ans1[i] + ans2[n - i + 1]);
	}
	printf("%d", s);
	return 0;
}

P1484 种树

题解:

一道很经典的反悔贪心题目。
若抛开数据范围不谈,我们很容易想到DP做法。
但数据范围很明显不允许我们这么做。
我们可以模拟一下选位置的过程。
不难发现,若\(x\)是序列中的最大值,要是要选择两个点,只有两种选择:选除去 \(x\) 两侧的一个点,或是不选择 \(x\),选择 \(x-1\) 和 \(x+1\)。
按照反悔贪心的套路去做的话,我们可以先选择 \(x\),再在后续的操作中判断是否需要反悔。
我们可以建立大根堆去储存每个点的权值,并根据位置建立一个双向链表。
每次操作时取出堆顶(一定要是未访问的),并将 \(i-1\) 和 \(i+1\) 合并为一个节点,下标设为\(i\),用于反悔,则该节点的权值应为 \(a_{l_i}+a_{r_i}-a_i\)。同时将 \(i-1\) 和 \(i+1\) 设为已访问,原因也很显然,若是需要反悔,应该取合并后的新节点而不是这两个节点。同时更新链表。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 7;
typedef long long ll;
int n, k;
ll a[N];
bool vis[N];
struct node{
	ll w, id;
	bool operator<(const node &a)const{
		return w < a.w;
	}
};
priority_queue<node>q;
int l[N], r[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> k;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
		q.push({a[i], i});
		l[i] = i - 1;
		r[i] = i + 1;
	}
	r[0] = 1;
	l[n + 1] = n;
	ll ans = 0;
	for(int i = 1; i <= k; i++){
		while(vis[q.top().id]) q.pop();
		node t = q.top();
		q.pop();
		if(t.w < 0) break;
		ans += t.w;
		int c = t.id;
		vis[l[c]] = vis[r[c]] = 1;
		a[c] = a[l[c]] + a[r[c]] - a[c]; 
		t.w = a[c];
		l[c] = l[l[c]];
		r[l[c]] = c;
		r[c] = r[r[c]];
		l[r[c]] = c;
		q.push(t);
	}
	printf("%lld\n", ans);
	return 0;
}

P4597 序列 sequence

题解:

思维含量很高的一道题目,
设当前在处理 \(x\),此前的最大值为 \(y\)。
显然,当 \(x \geq y\) 时,不用做任何处理。
当 \(x < y\) 时,不难发现,无论怎么处理,代价一定是 \(y-x\)。
但所带来的影响是不唯一的,会存在若干个二元组 \((x,x),(x+1,x+1),\dots ,(y,y)\)。
数值越小,对后续的处理就越方便。
那么将 \((x,x)\) 作为处理结果似乎为最优。
但不要忘了,其有可能不合法,即在前面的数中存在比 \(x\) 大的数。
换而言之,如果 \(x\) 为当前的次大值,那么 \(x\) 即可作为最大值,即 \(y\) 可以被 \(x\) “同化”。
我们可以弄一个大根堆,从前往后遍历。每次处理时,先将当前节点加入堆。若当前节点比堆顶小,加上堆顶与当前节点的差,把堆顶拿掉,加入当前节点,视作 \(y\) 被 \(x\) 同化了。

Code:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5e5 + 7;
int n;
ll a[N];
priority_queue<ll>q;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	ll ans = 0;
	for(int i = 1; i <= n; i++){
		q.push(a[i]);
		if(q.top() > a[i]){
			ans += q.top() - a[i];
			q.pop();
			q.push(a[i]);
		}
	}
	printf("%lld\n", ans);
	return 0; 
}

P8170 [eJOI 2021] Waterfront

题解:

我们设 \(f_i=h_i+m \cdot d_i\),即一棵树不做修剪所能长到的高度。
很显然,我们应该通过修剪使得最终 \(f_i\) 的最大值最小。
但是在修剪时,树的高度必须不小于 \(x\) 这一点比较难处理。
我们考虑可不可以先将每棵树砍的次数算出来,在后续的操作中再处理顺序。
我们很容易发现可以维护一个大根堆,每次修剪 \(f_i\) 最大的数。
但由于总共有 \(k \times m\) 次修剪,这一步操作的时间复杂度就已达到 \(O(km \log n)\)。
考虑优化,我们若是模拟几个数据,便不难发现我们可以先按 \(f_i\) 降序排序,每次砍伐区间 \([1,r]\) 内的树。若 \(f_r < f_{r+1}\),便将这个区间扩展到 \([1,r+1]\)。
这样我们便得到了每棵树砍伐的次数。
接下来我们需要给每一次砍伐安排日期。
由于在修剪时树的高度必须不小于 \(x\),因此将每一次砍伐尽量往前提肯定是最优的。
对于每棵树,从第一天开始遍历,若是当前高度可行,便一直砍伐直到高度不够,按照先前求出的次数去处理。
这样我们便初步安排了日期
但是由于每天只能砍 \(k\) 次,而我们的处理并没有考虑这一点,因此会存在一些日期砍了超过 \(k\) 次,我们便可以将一些砍伐往后移至后面第一个有空位的日期,这里可以用并查集实现。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 7, inf = 2e8;
int n, m, k, x;
int h[N], d[N], f[N];
int idx[N];
int cnt[N];
vector<int>cur, g[N];
int ans[N];
int fa[N], tim[N];
int get(int x){
	return (fa[x] == x ? x : fa[x] = get(fa[x]));
}
void merge(int x, int y){
	if(get(x)==get(y)) return;
	fa[get(x)] = get(y);
}
bool cmp1(int x, int y){
	return f[x] > f[y];
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m >> k >> x;
	for(int i = 1; i <= n; i++){
		cin >> h[i] >> d[i];
		f[i] = h[i] + d[i] * m;
		idx[i] = i;
	}
	for(int i = 1; i <= m + 1; i++) fa[i] = i;
 	sort(idx + 1, idx + n + 1, cmp1);
	int r = 1;
	while(cur.size() <= k * m){
		bool flg = 0;
		for(int i = 1; i <= r; i++){
			if(f[idx[i]] >= x){
				f[idx[i]] -= x;
				cnt[idx[i]]++;
				cur.emplace_back(idx[i]);
				flg = 1;	
			}
		}
		if(!flg) break;
		while(r < n && f[idx[r]] <= f[idx[r + 1]]){
			r++;
			for(int i = r; i > 1; i--){
				if(f[idx[i]] > f[idx[i - 1]]) swap(idx[i], idx[i - 1]);
			}
		}
	}
	for(int i = 1; i <= n; i++){
		int hh = h[i];
		for(int j = 1; j <= m && g[i].size() < cnt[i]; j++){
			hh += d[i];
			while(hh >= x && g[i].size() < cnt[i]){
				hh -= x;
				g[i].emplace_back(j);
			}
		}
	}
	for(int i = 1; i <= m; i++) tim[i] = k;
	for(int i : cur){
		int t = get(g[i][ans[i]]);
		if(t > m) break;
		tim[t]--;
		if(!tim[t]) merge(t, t + 1);
		ans[i]++;
	}
	int c = 0;
	for(int i = 1; i <= n; i++){
		c = max(c, h[i] + m * d[i] - ans[i] * x);
	}
	printf("%d\n", c);
	return 0; 
}

P14563 跳跃

题解:

考虑先预处理出每个点按照最优策略会跳到哪里。
设 \(g_i\) 为点 \(i\) 一步应该跳到哪里。
将序列分为若干个全 \(1\) 序列和全 \(0\) 序列。
对于所有全 \(0\) 序列,我们可以将其分成长度 \(\geq k\) 和长度 \(< k\)。
设其长度为 \(len\)。
对于前者,不难发现如果第一步落在这个序列的前 \(len mod k\) 个位置,那么就一定不优。
对于后者,则不能踩在其上面,因为一定存在一种方案可以跨过去。
同时因为第二问的约束,一步走的要尽可能远。
则转移方程为

\[g_i= \begin{cases} i+k &(i+k) \text{合法}\\ g_{i-1} &otherwise \end{cases} \]

但暴力跳的复杂度很明显是错误的,联想到求 \(LCA\) 的优化方式,因此树链剖分倍增优化。
将此前的定义改为 \(g_{i,j}\) 为从 \(i\) 出发跳 \(2^j\) 步的终点。
同时再处理一个 \(p_{i,j}\) 为从 \(i\) 出发跳 \(2^j\) 步所经过的黑色节点数量。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 7;
int Case;
int n, q, k, t;
string s;
bool is[N];
int g[N][20], p[N][20];
int lg[N];
void init(){
	for(int i = 2; i <= N - 7; i++) lg[i] = lg[i >> 1] + 1;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	init();
	cin >> Case >> n >> q >> k >> t;
	cin >> s;
	s = '1' + s;
	for(int i = 1, lst = 1; i <= n; i++){
		if(s[i] == '0') continue;
		else{
			is[i] = 1;
			if(i - lst >= k){
				int j = lst;
				while(j <= i && (i - j) % k) j++;
				for(; j <= i; j++){
					is[j] = 1;
				}
			}
			lst = i + 1;
		}
	}
	for(int i = 1; i <= n; i++){
		int nxt = min(i + k, n);
		if(is[nxt]){
			g[i][0] = nxt;
			p[i][0] = (s[nxt] == '0');
		}
		else g[i][0] = g[i - 1][0], p[i][0] = p[i - 1][0];
	}
	for(int j = 1; (1 << j) <= n; j++){
		for(int i = 1; i <= n; i++){
			g[i][j] = g[g[i][j - 1]][j - 1];
			p[i][j] = p[i][j - 1] + p[g[i][j - 1]][j - 1];
		}
	}
	while(q--){
		int l, r;
		cin >> l >> r;
		if(l > r) swap(l, r);
		int stp = 0, sum = 0;
		for(int i = lg[n]; i >= 0; i--){
			if(g[l][i] < r){
				stp += p[l][i];
				sum += (1 << i);
				l = g[l][i];
			}
		}
		if(t) printf("%d %d\n", stp, sum + 1);
		else printf("%d\n", stp);
	}
	return 0;
}

P2048 [NOI2010] 超级钢琴

感觉难度虚高,
题目的意思就是找到 \(k\) 个不同的长度介于 \([L,R]\) 的连续子序列使得和最大,
也就是找到前 \(k\) 大长度介于 \([L,R]\) 的连续子序列。
考虑对于前缀和数组构建一个 \(ST\) 表,
对于每个点作为左端点,查询最大的右端点便是一个 \(RMQ\) 问题,可以 \(O(1)\) 解决。
预处理出这个东西,全部丢到堆里面,每次取最大并将新的入队即可。

Code:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5e5 + 7;
int n, k, L, R;
int a[N];
ll s[N];
struct Sparse_Table{
	int lg2[N];
	ll c[N][27];
	int rev[N][27];
	void init(){
		for(int i = 2; i <= N - 7; i++) lg2[i] = lg2[i >> 1] + 1; 
		for(int i = 1; i <= n; i++) c[i][0] = s[i], rev[i][0] = i;
		for(int j = 1; (1 << j) <= n; j++){
			for(int i = 1; i + (1 << j) - 1 <= n; i++){
				c[i][j] = max(c[i][j - 1], c[i + (1 << (j - 1))][j - 1]);
				rev[i][j] = (c[i][j - 1] > c[i + (1 << (j - 1))][j - 1] ? rev[i][j - 1] : rev[i + (1 << (j - 1))][j - 1]);
			}
		}
	}
	int query(int l, int r){
		int d = lg2[r - l + 1];
		return (c[l][d] > c[r - (1 << d) + 1][d] ? rev[l][d] : rev[r - (1 << d) + 1][d]);
	}
}ST;
struct Node{
	int p, l, r;
	bool operator<(const Node &a)const{
		return s[ST.query(l, r)] - s[p - 1] < s[ST.query(a.l, a.r)] - s[a.p - 1];
	}
};
priority_queue<Node>q;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> k >> L >> R;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
		s[i] = s[i - 1] + a[i];
	}
	ST.init();
	for(int i = 1; i <= n; i++){
		if(i + L - 1 <= n) q.push({i, min(n, i + L - 1), min(n, i + R - 1)});
	}
	ll ans = 0;
	while(k--){
		Node tmp = q.top();
		q.pop();
		int p = tmp.p, l = tmp.l, r = tmp.r;
		int t = ST.query(l, r);
		ans += s[t] - s[p - 1];
		if(t != l) q.push({p, l, t - 1});
		if(t != r) q.push({p, t + 1, r});
		//printf("%d %d %d %d\n", p, l, r, t);
	}
	printf("%lld\n", ans);
	return 0;
}

P3045 [USACO12FEB] Cow Coupons G

水完了,首次自己切紫。
对于一头牛,对其会有三个决策:不买,用原价买,用优惠卷买。
这样不是很好反贪,考虑将一个物品拆分成用或不用优惠卷两个物品,这样对于每个物品就只需要考虑买或不买。
将优惠卷物品和非优惠卷物品分别放在两个堆中处理。
对于每次操作,取出当前最小的两类物品。
设其代价分别为 \(c_i\) 和 \(p_j\)。
那么分析一下取 \(c_i\) 更优的条件。
设 \(\delta_i\) 为 \(p_i - c_i\)。
很显然如果要替换,则必定是替换此前 \(\delta\) 最小的,设其为 \(k\)。
考虑求出替换前和替换后的代价。
替换前代价应为 \(c_k + min p_i(i未被处理)\),
替换后代价应为 \(p_k+c_i\)。
比较这两个式子大小即可。

Code:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 5e4 + 7;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, k;
ll m;
ll p[N], c[N];
bool used[N];
struct cow{
	int p;
	ll w;
	bool operator>(const cow a)const{
		return w > a.w;
	}
};
priority_queue<cow, vector<cow>, greater<cow> >pq, cq;
priority_queue<ll, vector<ll>, greater<ll> >g;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> k >> m;
	for(int i = 1; i <= n; i++){
		cin >> p[i] >> c[i];
		pq.push({i, p[i]});
		cq.push({i, c[i]});
	}
	for(int i = 1; i <= k; i++) g.push(0);
	ll cost = 0;
	int cnt = 0;
	while(pq.size() && cq.size()){
		int u = pq.top().p, v = cq.top().p;
		if(used[u]){
			pq.pop();
			continue;
		}
		if(used[v]){
			cq.pop();
			continue;
		}
		if(p[u] - c[v] > g.top()){
			if(cost + g.top() + c[v] > m) break;
			cq.pop();
			cnt++;
			cost += g.top() + c[v];
			g.pop();
			g.push(p[v] - c[v]);
			used[v] = 1;
		}
		else{
			if(cost + p[u] > m) break;
			pq.pop();
			cost += p[u];
			cnt++;
			used[u] = 1;
		}
	}
	printf("%d\n", cnt);
	return 0;
}
posted @ 2026-03-05 20:21  EzSun599  阅读(23)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3