ABC460贪心|多源BFS|数论|计数|线段树|树的直径

C

贪心

a数组长度n,b数组长度m,每次取一对a,b可以拼成一个成品。要求b至少是a的两倍。问最多能拼出来多少个?

不妨考虑每个b能否找到一个a(如果考虑每个a能否找到b其实是同理的),那么对b来说,a至少要是当前bi的一半大,所以越大的a,越通用,小的a只能给晓得b用。所以要尽可能配对,应该先用小的,如果不用后面可能就用不了了。

对a,b,都升序排序,枚举b,对于每个b,把小于b一半的a都删掉,因为后面的b更大了,如果当前bi都用不了,后面的更用不了。移除后最小的ai与当前bi配对。这样贪心,最后得到的配对就是最大配对。

c 复制代码
void solve() {
	int n, m;
	cin >> n >> m;
	vi a(n + 1), b(m + 1);
	rep(i, 1, n) {
		cin >> a[i];
	}
	rep(i, 1, m) {
		cin >> b[i];
	}

	sort(a.begin() + 1, a.end());
	sort(b.begin() + 1, b.end());

	queue<int> q;
	rep(i, 1, n) {
		q.push(a[i]);
	}


	int ans = 0;
	rep(i, 1, m) {
		int x = b[i];
		while (q.size() && q.front() * 2 < x) {
			q.pop();
		}
		if (q.size()) {
//			cout << q.front() << ' ' << x << '\n';
			ans++;
			q.pop();
		}
	}

	cout << ans << '\n';
}

D

思维 多源BFS

网格图上,每个黑点每秒会把相邻的白点染黑,相邻的定义是八联通。并且每秒原本的黑点会变成白点。问k=1e100秒后每个点的颜色?

不可能模拟,一定是有结论的,一般就是循环。打表或者注意到,可以发现,如果初始只有一个黑点,进入循环后,类似一个波的传播,八联通意义下,距离奇数的位置奇数秒时黑的,偶数秒是白的,距离偶数的位置反过来。如果有多个点相互叠加,那么一个点只要到最近的黑色源点,距离是奇数,那么奇数秒就是黑的,偶数秒是白的。

于是要求的就是初始,每个白点到最近的黑点距离。考虑黑点开始多源BFS即可。

这里有点小bug:一整块黑色,内部的黑色会在第一秒开始后变成白的,并且第二秒不会在编程黑的,和独立的黑色点不一样了,不能作为独立的波源了,可以视为不存在。

最简单的处理方法是,手动模拟第一秒,此时第一秒已经把整块黑色的情况都处理完了,从第一秒的结果开始,再跑上面说的多源BFS

我这里的代码没用这个处理,那么就要麻烦一点,需要从黑色,白色分别开始多源BFS,然后复杂分类讨论

c 复制代码
void solve() {
	int n, m;
	cin >> n >> m;
	vvi a(n + 1, vi(m + 1));
	int cnt = 0;
	rep(i, 1, n) {
		rep(j, 1, m) {
			char c;
			cin >> c;
			if (c == '#') {
				a[i][j] = 1;
				cnt++;
			}
		}
	}

	if (!cnt || cnt == n * m) {
		rep(i, 1, n) {
			rep(j, 1, m) {
				cout << '.';
			}
			cout << '\n';
		}
		return;
	}

	queue<pii>q;
	vvi d(n + 1, vi(m + 1, -1));
	rep(i, 1, n) {
		rep(j, 1, m) {
			if (a[i][j]) {
				q.push({i, j});
				d[i][j] = 0;
			}
		}
	}

	while (q.size()) {
		auto [x, y] = q.front();
		q.pop();

		rep(i, -1, 1) {
			rep(j, -1, 1) {
				if (!i && !j)continue;
				int nx = x + i, ny = y + j;
				if (nx < 1 || nx > n || ny < 1 || ny > m)continue;
				if (d[nx][ny] == -1) {
					d[nx][ny] = d[x][y] + 1;
					q.push({nx, ny});
				}
			}
		}
	}

	queue<pii>q1;
	vvi d1(n + 1, vi(m + 1, -1));
	rep(i, 1, n) {
		rep(j, 1, m) {
			if (!a[i][j]) {
				q1.push({i, j});
				d1[i][j] = 0;
			}
		}
	}

	while (q1.size()) {
		auto [x, y] = q1.front();
		q1.pop();

		rep(i, -1, 1) {
			rep(j, -1, 1) {
				if (!i && !j)continue;
				int nx = x + i, ny = y + j;
				if (nx < 1 || nx > n || ny < 1 || ny > m)continue;
				if (d1[nx][ny] == -1) {
					d1[nx][ny] = d1[x][y] + 1;
					q1.push({nx, ny});
				}
			}
		}
	}


	rep(i, 1, n) {
		rep(j, 1, m) {
			if (a[i][j]) {
				if (d1[i][j] % 2) {
					cout << '#';
				} else {
					cout << '.';
				}
			} else {
				if (d[i][j] % 2) {
					cout << '.';
				} else {
					cout << '#';
				}
			}
		}
		cout << '\n';
	}



}

E

数论 计数

问有多少x,y<=N,满足xy的字符串拼接得到的数字,模m意义下和x+y同余

核心数学转化:首先,我们来看定义 concat(x,y)\text{concat}(x, y)concat(x,y)。设正整数 yyy 在十进制下的位数(长度)为 LLL。那么将 xxx 和 yyy 拼接在一起,在数学上等价于将 xxx 向左移动 LLL 位,再加上 yyy:concat(x,y)=x⋅10L+y\text{concat}(x, y) = x \cdot 10^L + yconcat(x,y)=x⋅10L+y题目要求的同余条件为:concat(x,y)≡x+y(modM)\text{concat}(x, y) \equiv x + y \pmod Mconcat(x,y)≡x+y(modM)将上面的代数式代入:x⋅10L+y≡x+y(modM)x \cdot 10^L + y \equiv x + y \pmod Mx⋅10L+y≡x+y(modM)两边同时减去 yyy:x⋅10L≡x(modM)x \cdot 10^L \equiv x \pmod Mx⋅10L≡x(modM)再移项提公因式,得到最终的判定方程:x⋅(10L−1)≡0(modM)x \cdot (10^L - 1) \equiv 0 \pmod Mx⋅(10L−1)≡0(modM)

关键结论:通过上述变形,我们发现一个至关重要的性质:xxx 是否满足条件,只取决于 yyy 的十进制位数 LLL,而与 yyy 的具体数值无关。

既然只与 LLL 相关,而 y≤Ny \le Ny≤N,那么 LLL 的取值范围是非常有限的(如果 N≤1018N \le 10^{18}N≤1018,max⁡(L)=18\max(L) = 18max(L)=18;如果 N≤109N \le 10^9N≤109,max⁡(L)=9\max(L) = 9max(L)=9)。我们可以直接枚举 yyy 的位数 LLL。

对于一个固定的位数 LLL:

  • 计算合法的 yyy 的个数满足位数恰好为 LLL 且不超过 NNN 的正整数 yyy 的区间为 max⁡(1,10L−1),min⁡(N,10L−1)\\max(1, 10\^{L-1}), \\min(N, 10\^L - 1)max(1,10L−1),min(N,10L−1)。如果左端点大于右端点,说明不存在这样的 yyy,直接跳过。否则,合法的 yyy 的数量为:county(L)=min⁡(N,10L−1)−10L−1+1\text{count}_y(L) = \min(N, 10^L - 1) - 10^{L-1} + 1county(L)=min(N,10L−1)−10L−1+1
  • 计算合法的 xxx 的个数对于固定的 LLL,我们需要在 1,N1, N1,N 中寻找有多少个 xxx 满足:x⋅(10L−1)≡0(modM)x \cdot (10^L - 1) \equiv 0 \pmod Mx⋅(10L−1)≡0(modM)这等价于:MMM 必须整除 x⋅(10L−1)x \cdot (10^L - 1)x⋅(10L−1)。为了让 xxx 尽可能小,我们可以消除 10L−110^L - 110L−1 和 MMM 的公共部分。令:g=gcd⁡(10L−1,M)g = \gcd(10^L - 1, M)g=gcd(10L−1,M)那么上面的条件等价于 xxx 必须是 Mg\frac{M}{g}gM 的倍数。令 step=Mgstep = \frac{M}{g}step=gM,则 xxx 必须满足:x≡0(modstep)x \equiv 0 \pmod{step}x≡0(modstep)因为 1≤x≤N1 \le x \le N1≤x≤N,所以在这个范围内满足条件的 xxx 的个数非常容易计算,即:countx(L)=⌊Nstep⌋\text{count}_x(L) = \lfloor \frac{N}{step} \rfloorcountx(L)=⌊stepN⌋
  • 乘法原理求和对于当前的位数 LLL,xxx 和 yyy 的选择是相互独立的。因此,当前长度带来的合法对数 (x,y)(x, y)(x,y) 为:AnsL=countx(L)×county(L)\text{Ans}_L = \text{count}_x(L) \times \text{count}_y(L)AnsL=countx(L)×county(L)将所有可能的 LLL 对应的结果累加,并在过程中对 998244353998244353998244353 取模,即可得到最终答案。
c 复制代码
void solve() {
	int n, m;
	cin >> n >> m;

	i128 p = 1;
	int ans = 0;
	while (p <= n) {
		i128 hi = p * 10 - 1;
		int g = gcd((int)(hi % m), m);
		int c1 = n / (m / g);

		i128 mx = hi;
		if (hi > n)mx = n;

		int c2 = mx - p + 1;

		c1 %= M2, c2 %= M2;
		ans = (ans + c1 * c2 % M2) % M2;
		p *= 10;
	}

	cout << ans << '\n';


}

F

树的直径 线段树 LCA

树上点初始黑色,每次操作把一个点的颜色反转,然后求树上两个黑色点之间的最大距离。

树上一个点集的最大距离,考虑包含这些点的子树,就是这个子树的直径,树的直径有一个很好的性质,增加一个点z,原本的直径xy,新的直径只可能是xy,xz,yz三者之一。

所以对于这个问题,白色点染黑时,只用考虑新增的黑点,和原有子树内的直径,产生的新的解,取max。问题是黑点变白,相当于删点,不好做。

删除不好做,一般的思路是不删除,改成撤销,或者用线段树合并编程只有增加,没有删除。

第一个思路简单说一下,就是线段树分治的思想,不好删除,那么在dfs返回的时候直接回滚答案。实现起来有点麻烦

第二个思路实现起来更自然,就是我们把每个点都放到线段树的叶子上,每次合并两个区间的点,假设两个区间的直径xy,pq都是确定的,那么合并后整个区间的直径,只可能是xy,pq,xp,xq,yp,yq这几个中的一个,分别计算长度,取max即可。快速计算树上路径长度,可以用LCA配合深度。

注意这里虽然把树放到序列上建线段树了,但是并不需要dfs序,加上也没错,但是不必要。

c 复制代码
struct Tree {
#define ls u<<1
#define rs u<<1|1
	struct Node {
		int l, r, x, y, ans;

		Node operator+(const Node &o) {
			Node res;
			res.l = l;
			res.r = o.r;

			res.ans = -1;

			if (ans == -1) {
				res.x = o.x;
				res.y = o.y;
				res.ans = o.ans;
			} else if (o.ans == -1) {
				res.x = x;
				res.y = y;
				res.ans = ans;
			}

			else {
				auto cal = [&](int x, int y)->void{
					int t = dis(x, y);
					if (t > res.ans) {
						res.ans = t;
						res.x = x, res.y = y;
					}
				};
				cal(x, y);
				cal(o.x, o.y);

				cal(x, o.x);
				cal(x, o.y);

				cal(y, o.x);
				cal(y, o.y);
			}

			return res;
		}
	} tr[N << 2];

	void pushup(int u) {
		tr[u] = tr[ls] + tr[rs];
	}

	void build(int u, int l, int r) {
		tr[u] = {l, r, l, l, 0};
		if (l == r)	return;
		int mid = (l + r) >> 1;
		build(ls, l, mid);
		build(rs, mid + 1, r);
		pushup(u);
	}

	void modify(int u, int idx, int val = -1) {
		if (tr[u].l == tr[u].r) {
			if (tr[u].ans == -1) {
				tr[u].x = tr[u].y = idx;
				tr[u].ans = 0;
			} else {
				tr[u].ans = -1;
			}
			return;
		} else {
			int mid = (tr[u].l + tr[u].r) >> 1;
			if (mid >= idx)	modify(ls, idx, val);
			else modify(rs, idx, val);
			pushup(u);
		}
	}

	Node query(int u, int l, int r) {
		if (l <= tr[u].l && tr[u].r <= r)	return  tr[u];
		int mid = (tr[u].l + tr[u].r) >> 1;
		if (r <= mid)return query(ls, l, r);
		if (l > mid)return query(rs, l, r);
		return query(ls, l, r) + query(rs, l, r);
	}
} t;

void solve() {
	int n;
	cin >> n;

	rep(i, 2, n) {
		lg[i] = lg[i >> 1] + 1;
	}
	vvi g(n + 1);
	rep(i, 1, n - 1) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}

	auto &&dfs = [&](auto &&dfs, int u, int fa)->void{
		F[0][u] = fa;
		rep(i, 1, lg[d[u]]) {
			F[i][u] = F[i - 1][F[i - 1][u]];
		}

		for (int v : g[u]) {
			if (v == fa)continue;
			d[v] = d[u] + 1;
			dfs(dfs, v, u);
		}
	};
	dfs(dfs, 1, 0);

	t.build(1, 1, n);

	int q;
	cin >> q;
	rep(i, 1, q) {
		int x;
		cin >> x;
		t.modify(1, x);

		cout << t.query(1, 1, n).ans << '\n';
	}
	
}
相关推荐
小欣加油1 小时前
leetcode121买卖股票的最佳时机
数据结构·c++·算法·leetcode·职场和发展
暖阳华笺2 小时前
【高频考点】K-Means聚类算法
c++·算法·机器学习·kmeans·聚类
下午写HelloWorld2 小时前
后量子密码算法:协同签名研究综述
算法·密码学·后量子·协同签名
小蒋学算法2 小时前
算法-计算右侧小于当前元素的个数-分治&归并思想
java·数据结构·算法
lqqjuly2 小时前
FlashAttention 深度解析
人工智能·深度学习·算法
满怀冰雪2 小时前
第05篇-滑动窗口算法-一套模板解决子串与子数组问题
java·算法
叫我:松哥2 小时前
基于LSTM与ARIMA的城市空气质量分析与预测系统
人工智能·python·rnn·算法·机器学习·flask·lstm
j7~2 小时前
【C++】模板初阶--函数模板,类模板详解
数据结构·c++·算法·函数模板·类模板·函数模板实例化
无限码力2 小时前
阿里算法岗 0530笔试真题 - 寻找满足条件的最优子序列
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试真题·阿里算法题