「赛后总结」暑假集训:20230724 CSP 模拟赛

「赛后总结」20230724 CSP 模拟赛

点击查看目录

想听歌,想看巨人,但是没有条件。

总结。

rk1 三个首杀,前二没有 HZOI 土著,前三没有 HZOI 2022 人,咋整的呀?

T1 5min 过掉样例交了一发,然后手玩一个样例不小心 Hack 掉了,改完了手玩一个样例又 Hack 掉了,最后对拍观察数据猜出正解,可见对拍的重要性。

T2 其实是有实力过的,赛时已经想到了 \(n^2\) 写法及优化,但是一直以为 \(n^2\) 暴力写假了就没时间去优化了,这种错觉的产生原因是搜索部分的暴力因为哈希冲突挂了,大概最后 20min 才发现。

T3 假二分能 65 分,据 xuany 说加特判 78 分,搬题人能不能用点心。

Update:最新成果,100 分,所有测试点均在 30ms 以内

T4 看出来是状压但不会压。

这就是睡觉的重要性,实测多睡两个小时可以前进 11 名。

题解

T1 最长上升子序列 ARC125C

思路

sto Chen_jr orz

代码

点击查看代码
const ll N = 2e5 + 10;
namespace SOLVE {
	ll n, k, m, a[N], vis[N], ans[N];
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline void In () {
		n = rnt (), k = rnt (), m = 0;
		_for (i, 1, k) vis[a[i] = rnt ()] = 1;
		return;
	}
	inline void Solve () {
		ll j = 1;
		_for (i, 1, k - 1) {
			ans[++m] = a[i];
			while (vis[j]) ++j;
			if (j > a[i]) continue;
			ans[++m] = j, ++j;
		}
		for_ (i, n, a[k] + 1) if (!vis[i]) ans[++m] = i;
		ans[++m] = a[k];
		for_ (i, a[k] - 1, j) if (!vis[i]) ans[++m] = i;
		return;
	}
	inline void Out () {
		_for (i, 1, n) printf ("%lld ", ans[i]);
		puts ("");
		return;
	}
}

T2 独特序列 ARC125D

思路

\(f_i\) 表示以 \(i\) 结尾有多少个合法序列,有转移:

\[f_i = \sum_{j = 1}^{i - 1}(\prod_{k = j + 1}^{i - 1}[a_k\ne a_i][a_k\ne a_j])f_j \]

答案为:

\[\sum_{i = 1}^{n}(\prod_{k = i + 1}^{n}[a_k\ne a_i])f_i \]

预处理可做到 \(O(n^2)\)

不难发现对于 \(j < i\),当且仅当 \(a_j\) 这个数在 \([1, i - 1]\) 中是最后一次出现时且 \((j, i)\) 中不存在 \(a_i\) 这个数时才会转移到 \(f_i\),那我们把没用的及时清零,使用 BIT 维护。

代码

点击查看代码
const ll N = 2e5 + 10, P = 998244353;

namespace BIT {
	class BIT {
	public:
		ll n;
	private:
		ll b[N];
		inline ll lowbit (ll x) { return x & -x; }
	public:
		inline void Update (ll x, ll y){
			if (x <= 0) return;
			while (x <= n) b[x] = (b[x] + y + P) % P, x += lowbit (x);
			return;
		}
		inline ll Query (ll x){
			ll sum = 0;
			while (x > 0) sum = (sum + b[x]) % P, x -= lowbit (x);
			return sum;
		}
	};
}

namespace SOLVE {
	ll n, a[N], la[N], f[N], vis[N], ans;
	BIT::BIT tr;
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline void In () {
		tr.n = n = rnt ();
		_for (i, 1, n) {
			a[i] = rnt ();
			la[i] = vis[a[i]], vis[a[i]] = i;
		}
		return;
	}
	inline void Solve () {
		_for (i, 1, n) {
			f[i] = (!la[i] + tr.Query (i - 1) - tr.Query (la[i] - 1) + P) % P;
			tr.Update (la[i], -f[la[i]]), f[la[i]] = 0, tr.Update (i, f[i]);
		}
		ans = tr.Query (n);
		return;
	}
	inline void Out () {
		printf ("%lld\n", ans);
		return;
	}
}

T3 最大 GCD ARC126C

思路

这玩意没有单调性,不能二分。

image

首先考虑能不能全加到最大值,能的话全加上去,还有剩余就平均分。

这个值域很小,那么枚举最大公约数,考虑如何快速判断解是否可行。即解不等式:

\[\begin{aligned} \sum_{i = 1}^{n}\left\lceil\frac{a_i}{x}\right\rceil - a_i &\le k\\ \sum_{i = 1}^{n}\left\lceil\frac{a_i}{x}\right\rceil &\le k + \sum_{i = 1}^{n}a_i \end{aligned} \]

考虑快速求左边这玩意,发现取值只有 \(\left\lceil\frac{\max_{i = 1}^{n}a_i}{x}\right\rceil\) 中,开个桶然后前缀和记录。

代码

点击查看代码
const ll N = 1e6 + 10;
namespace SOLVE {
	ll n, k, sum, mx, mn, a[N], t[N], ans;
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline bool Check (ll x) {
		ll q = -sum;
		_for (i, 1, (mx - 1) / x + 1) q += (t[i * x] - t[(i - 1) * x]) * i * x;
		return q <= k;
	}
	inline void In () {
		n = rnt (), k = rnt ();
		_for (i, 1, n) {
			a[i] = rnt ();
			++t[a[i]], sum += a[i];
			mx = std::max (mx, a[i]);
			mn = std::min (mn, a[i]);
		}
		return;
	}
	inline void Solve () {
		_for (i, 1, mx * 2) t[i] += t[i - 1];
		if (mx * n - sum <= k) {
			ans = mx + (k - mx * n + sum) / n;
			return;
		}
		for_ (i, mx, 1) if (Check (i)) { ans = i; break; }
		return;
	}
	inline void Out () {
		printf ("%lld\n", ans);
		return;
	}
}

T4 连续子段 ARC126D

思路

考虑状压。设 \(f_{i, j}\) 表示当前到了第 \(i\) 个数,当前排好的数的情况为 \(j\),考虑转移。

如果把 \(a_i\) 排进来,它肯定是从后往前移,次数为当前排好的数中比 \(a_i\) 大的数的数量。

不弄进来的话,当前序列还需要统一向右挪一次,或者让以后排进来的数多向左挪一次。

为方便直接设 \(s(x)\) 表示 \(\operatorname{popcount}(x)\),转移方程:

\[f_{i, j} = \min\{f_{i - 1, j\oplus 2^{a_i - 1}} + s(\left\lfloor\frac{j}{2^{a_i}}\right\rfloor), f_{i - 1, j} + s(j), f_{i - 1, j} + s(2^k - 1) - s(j)\} \]

可以滚一维。

代码

点击查看代码
const ll N = 210, S = 1 << 16, inf = 1ll << 40;
namespace SOLVE {
	ll n, k, a[N], f[S], ans;
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline ll Count (ll x) { ll s = 0; while (x) s += x & 1, x >>= 1; return s; }
	inline void In () {
		n = rnt (), k = rnt ();
		_for (i, 1, n) a[i] = rnt ();
		return;
	}
	inline void Solve () {
		memset (f, 0x3f, sizeof (f));
		f[0] = 0;
		_for (i, 1, n) {
			ll x = a[i];
			for_ (j, (1 << k) - 1, 0) {
				if (f[j] >= inf) continue;
				if (!((1 << (x - 1)) & j)) 
					f[j | (1 << (x - 1))] = std::min (f[j | (1 << (x - 1))], f[j] + __builtin_popcount (j >> (x - 1)));
				f[j] += std::min (__builtin_popcount (j), (int)(k) - __builtin_popcount (j));
			}
		}
		return;
	}
	inline void Out () {
		printf ("%lld\n", f[(1 << k) - 1]);
		return;
	}
}
posted @ 2023-07-24 19:42  K8He  阅读(94)  评论(2编辑  收藏  举报