【MX-S7】梦熊 NOIP 2024 模拟赛 3 & SMOI Round 2(同步赛)题解

P11323 【MX-S7-T1】「SMOI-R2」Happy Card

原题链接

特殊性质分没过!!!

这道题是有性质的:就是三带一最多有两组。
来举个例子:
9 9 9 1 1 1 3 3 3 2 2 2,那么可以出三次三带一,也可以出一次三带一,两次炸弹, 4 4 4是优于 3 3 3的,所以第二个更好一点。

来看一下特殊性质的分:

  • v i ≤ 2 v_i\le2 vi2时,直接出对子或者单张就行
  • v i ≤ 3 v_i\le3 vi3时,先把三带一出完,再出对子,最后出单排。
  • v i ≤ 5 v_i\le5 vi5时,这个怎么出就视情况而定了。比如:有 5 5 5 1 1 1 5 5 5 2 2 2,这种情况就不能盲目的出炸弹或者三带一,比如可以出两次三带一,两次单张,一共 4 4 4次,但明显不是最优解,最优的话出一个炸弹一个三带一一个对子,一共 3 3 3次。

来想正解:

如果出完了单牌和对子还剩三张的,剩下的三张牌可以一起
出出去,并且会达到答案下界 ⌊ 4 n ⌋ + ⌊ n m o d    4 2 ⌋ + ( n m o d    2 ) \left \lfloor \frac{4}{n} \right \rfloor + \left \lfloor \frac{n\\\mod\\4}{2} \right \rfloor + (n\mod\\2) n4+2nmod4+(nmod2)

否则考虑三带一如何出,先出单张,再出对子。

贪心的做法:

以发现,三带一和炸弹可以合并为三个相同的带任意一张牌。那我们尽量都选三张相同的,这样每种牌最后只剩 0 , 1 , 2 0,1,2 012张牌,我们先用三张带走尽量带走只剩 1 张的牌,如果还有多的三张,就去不断带只剩 2 张的(花费两个三张),如果两张带完还有剩的,用三带自己带自己。

c o d e code code

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300010;
int n, m;
int g[N];
LL t[3];
int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
		x = x * 10 + ch - '0', ch = getchar();
	return x * f;
}
int main() {
	int T;
	cin >> T;
	while (T -- ) {
		cin >> n;
		memset(t, 0, sizeof t);
		LL cnt = 0; // 三带的数量(三张相同)
		for (int i = 1; i <= n; i ++ ) {
			g[i] = read();
			cnt += g[i] / 3;
			t[g[i] % 3] ++ ;
		}
		if (t[1] > cnt) cout << t[1] - cnt + cnt + t[2] << endl; // 带不走全部1
		else {
			if ((cnt - t[1]) / 2 >= t[2]) { // 还能带走2
				LL x = cnt - t[1] - 2 * t[2]; // 剩余三带数量
				LL sum = t[1] + 2 * t[2] + x * 3 / 4; // x * 3 / 4是三带带自己
				if (x * 3 % 4 == 3) sum += 2; // 带完还剩三张相同,(不能直接输出三张相同),就分为一个单牌和一个对子
				else if (x * 3 % 4 == 2 || x * 3 % 4 == 1) sum ++ ; // 直接输出对子/单牌
				cout << sum << endl;
			} // 带不走2
			else cout << cnt + t[2] - (cnt - t[1]) / 2 << endl;
		} // (cnt - t[1]) / 2 是还能带走2的的数量
	}

	return 0;
}

T2 P11324 【MX-S7-T2】「SMOI-R2」Speaker


原题链接

这道题做出来了。

先来想想暴力思路:

首先,需要构建城市和道路的图结构。由于城市和道路构成一棵树,可以使用邻接表来表示这个图。

对于每一天的安排,需要找到从 x i x_i xi y i y_i yi路径上的所有城市,计算经过这些城市的费用,并选择一个使利润最大的城市作为第二场演讲的地点。

可以使用深搜来找到从 x i x_i xi y i y_i yi的路径,并计算路径上的费用就可以了。

接下来想想正解:

正解是用到了树链剖分+LCA。

考虑将答案拆分成两个部分。

第一个部分为无论如何选择 z i z_i zi都必然获得的利润,显然,这个值是两端点城市演讲收入之和减去两城市之间路费的值。

第二个部分为选择 z i z_i zi所获得的额外利润。额外利润的值是 z i z_i zi的演讲收入减去从 z i z_i zi走到两城市之间路径上所需的最少路费的 2 2 2倍。因为 z i z_i zi是第二个城市,所以需要从路径的某一个点开始走到 z i z_i zi再原路返回。

d ( i , j ) d(i,j) d(i,j)为从城市 i i i到城市 j j j需要花费的最少路费, d ( i , ( u , v ) ) d(i,{(u,v)}) d(i,(u,v))为从城市 i i i走到路径 ( u , v ) (u,v) (u,v)上需花费的最少路费。显然,当 i i i ( u , v ) (u,v) (u,v)上时, d ( i , ( u , v ) ) = 0 d(i,(u,v))=0 d(i,(u,v))=0

答案可表示为 c x + c y − d ( x , y ) + c z − 2 d ( z , ( x , y ) ) c_x+c_y-d_(x,y)+c_z-2d(z,(x,y)) cx+cyd(x,y)+cz2d(z,(x,y))

答案的前三个量都可以通过树上前缀和点这里轻松求得。考虑如何快速求最后两个量。

将最后两项捆绑起来求。

考虑对于每一个点 x x x max ⁡ 1 ≤ i ≤ n ( c i − 2 d ( x , i ) ) \max_{1 \le i \le n}(c_i-2d(x,i)) max1in(ci2d(x,i))

首先,可以按照树上广义后序遍历顺序求出 max ⁡ i ∈ S x ( c i − 2 d x , i ) \max _{i \in S_{x}}\left(c_{i}-2 d_{x, i}\right) maxiSx(ci2dx,i),其中 S x S_x Sx为节点 x x x的子树内节点集合。令上述值为 r x r_x rx,则 r x ← max ⁡ i ∈ s x ( r i − 2 d x , i ) r_{x} \leftarrow \max _{i \in s_{x}}\left(r_{i}-2 d_{x, i}\right) rxmaxisx(ri2dx,i)
,其中 S x S_x Sx为节点 x x x的儿子节点集合。

接下来,我们可以把子树信息和除了父节点以外的信息拼起来组成以每个节点为根的整棵树的答案,也就是按照树上广义前序遍历顺序求出 max ⁡ 1 ≤ i ≤ n ( c i − 2 d ( x , i ) ) \max_{1 \le i \le n}(c_i-2d(x,i)) max1in(ci2d(x,i)),转移方法为 r x ← max ⁡ ( r x , r f x − 2 d ( x , ( f x ) ) ) r_{x} \leftarrow \max(r_x,r_{f_x}-2d(x,(f_x))) rxmax(rx,rfx2d(x,(fx)))
,其中 f x f_x fx x x x节点的父亲。特别的,不对遍历起点的节点进行此转移。

有了以上的信息后,使用树链剖分在每次询问时求解 max ⁡ ( c z − 2 d ( z , ( x , y ) ) ) \max (c_z-2d(z,(x,y))) max(cz2d(z,(x,y)))即可。

c o d e code code

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, q, f[N], siz[N], ds[N], dfn[N], tp[N], idx, rnk[N], dep[N];
using ll = long long;
ll c[N], mx[N], dsv[N], fv[N];
using pil = pair<int, ll>;
vector<pil> road[N];
int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
		x = x * 10 + ch - '0', ch = getchar();
	return x * f;
}
void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
void dfs_init(int x) {
	dep[x] = dep[f[x]] + 1;
	mx[x] = c[x];
	siz[x] = 1;
	for (auto &[i, v] : road[x]) {
		if (i == f[x])
			continue;
		f[i] = x;
		fv[i] = v;
		dfs_init(i);
		siz[x] += siz[i];
		if (siz[i] > siz[ds[x]]) {
			ds[x] = i;
			dsv[x] = v;
		}
		mx[x] = max(mx[x], mx[i] - v * 2);
	}
}
void dfs_build(int x, int ttp) {
	dfn[x] = ++idx;
	rnk[idx] = x;
	tp[x] = ttp;
	if (!ds[x])
		return;
	mx[ds[x]] = max(mx[ds[x]], mx[x] - dsv[x] * 2);
	dfs_build(ds[x], ttp);
	for (auto &[i, v] : road[x]) {
		if (i == f[x] or i == ds[x])
			continue;
		mx[i] = max(mx[i], mx[x] - v * 2);
		dfs_build(i, i);
	}
}
ll mxtr[N << 2], edv[N << 2];
void build(int x, int l, int r) {
	if (l == r) {
		mxtr[x] = mx[rnk[l]];
		edv[x] = fv[rnk[l]];
		return;
	}
	int mid = (l + r) >> 1;
	build(x << 1, l, mid);
	build(x << 1 | 1, mid + 1, r);
	edv[x] = edv[x << 1] + edv[x << 1 | 1];
	mxtr[x] = max(mxtr[x << 1], mxtr[x << 1 | 1]);
}
using pll = pair<ll, ll>;
pll query(int x, int l, int r, int lb, int rb) {
	if (l >= lb and r <= rb)
		return {mxtr[x], edv[x]};
	pll res = {0, 0};
	int mid = (l + r) >> 1;
	if (lb <= mid) {
		auto [r1, r2] = query(x << 1, l, mid, lb, rb);
		res.first = max(res.first, r1);
		res.second += r2;
	}
	if (rb > mid) {
		auto [r1, r2] = query(x << 1 | 1, mid + 1, r, lb, rb);
		res.first = max(res.first, r1);
		res.second += r2;
	}
	return res;
}
pll query(int l, int r) {
	pll res = {0, c[l] + c[r]};
	while (tp[l] != tp[r]) {
		if (dep[tp[l]] > dep[tp[r]])
			swap(l, r);
		auto [r1, r2] = query(1, 1, n, dfn[tp[r]], dfn[r]);
		res.first = max(res.first, r1);
		res.second -= r2;
		r = f[tp[r]];
	}
	if (dep[l] > dep[r])
		swap(l, r);
	auto [r1, r2] = query(1, 1, n, dfn[l], dfn[r]);
	res.first = max(res.first, r1);
	res.second -= r2;
	res.second += fv[l];
	return res;
}
int main() {
	n=read(),q=read();
	for (int i = 1; i <= n; i++) {
		c[i]=read();
	}
	for (int i = 1; i < n; i++) {
		int x,y,z;
		x=read(),y=read(),z=read();
		road[x].emplace_back(y, z);
		road[y].emplace_back(x, z);
	}
	dfs_init(1);
	dfs_build(1, 1);
	build(1, 1, n);
	for (int i = 1, l, r; i <= q; i++) {
		l=read(),r=read();
		auto [r1, r2] = query(l, r);
		write(r1+r2);
		cout<<endl;
	}
}

T3 P11325 【MX-S7-T3】「SMOI-R2」Monotonic Queue


原题链接

T4 P11326 【MX-S7-T4】「SMOI-R2」XA-Game


原题链接

什么,你想问,关于这两道题我为什么没写题解?

别问,问就是不会!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值