[赛记] 暑假集训CSP提高模拟7,8,10

学长pig 出题规律:T1签到题,T2套路题(但没见过),T3神奇题(赛时想的做法几乎都是错的),T4peppapig题
学长pig:今天T3防AK
peppapig:今天比赛防爆零

A. Permutations & Primes 20pts

签到题,可惜没有签到;

显然,我们要让经过1的区间最多,所以将1放在序列中间;

除了1,就是2和3,所以我们把2和3放在两边,这样就可以保证中间及不同时覆盖两边且覆盖到1的区间都是合法的;

然后随便填,就完了;

赛时将1和2放在了一块,导致20pts;

其实也可以将合数放中间,1放最中间,质数放两边,这样也是最大的,就是需要筛一下;

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int t;
int n;
int a[500005];
bool pri[500005];
bool vis[500005];
void w(int x) {
	for (int i = 2; i <= x; i++) {
		if (!vis[i]) {
			vis[i] = true;
			pri[i] = true;
			for (int j = 2; i * j <= x; j++) {
				vis[i * j] = true;
			}
		}
	}
}
int main() {
	cin >> t;
	w(300005);
	while(t--) {
		cin >> n;
		if (n == 1) {
			cout << 1 << endl;
			continue;
		}
		if (n == 2) {
			cout << 2 << ' ' << 1 << endl;
			continue;
		}
		a[n / 2 + 1] = 1;
		a[1] = 2;
		a[n] = 3;
		int x = 4;
		for (int i = 2; i <= n / 2; i++) {
			a[i] = x;
			x++;
		}
		for (int i = n / 2 + 2; i <= n - 1; i++) {
			a[i] = x;
			x++;
		}
		for (int i = 1; i <= n; i++) {
			cout << a[i] << ' ';
		}
		cout << endl;
	}
	return 0;
}

B. 树上游戏 47pts

原题:ARC116E

貌似又是套路题。。。

我们可以贪心的来考虑,假设现在我们所允许的不满意度(危险度)为 dis,那么从叶子节点开始向上 dis 个长度需要有一个,然后这颗子树我们就可以删去不考虑了;

发现:如果知道答案,那么验证这个答案的合法性就变得比较容易;

做法:二分答案;

具体的,我们进行 dfs,统计每个节点的危险度 f[x] 和它距离它的没有删去的儿子的最长距离 g[x],遍历完这个节点的所有儿子后统计答案即可;

最后判断当前选的点数与 k 的大小关系完成 check

赛时居然想到用找重心的方法去做,很显然是错的(新知识没掌握导致的,总是会乱想是不是刚学的)。又乱搞推出了一个看起来不太对的“通式”,整了47pts;

看来现在赛时想出正解确实不容易啊。。。

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, k;
struct sss{
	int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
	e[++cnt].ne = h[u];
	h[u] = cnt;
	e[cnt].t = v;
}
int f[1000005], g[1000005];
int dfs(int x, int fa, int dis) {
	int res = 0;
	bool vis = false;
	f[x] = 0x3f3f3f3f;
	g[x] = -0x3f3f3f3f;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == fa) continue;
		vis = true;
		res += dfs(u, x, dis);
		f[x] = min(f[x], f[u] + 1);
		g[x] = max(g[x], g[u] + 1);
	}
	if (f[x] + g[x] <= dis) { //最大的危险程度可以接受,直接删除这棵子树;
		g[x] = -0x3f3f3f3f;
	}
	if (!vis) { //是叶子节点;
		g[x] = 0;
	}
	if (f[x] > dis) g[x] = max(g[x], 0); //危险程度太高,需要等祖先设立;
	if (g[x] == dis) { //此时必须设立,要不后代就不满足要求了;
		res++;
		f[x] = 0;
		g[x] = -0x3f3f3f3f; //删除这棵子树;
	}
	return res;
}
bool ck(int x) {
	return (dfs(1, 0, x) + (g[1] >= 0)) <= k; //根节点需不需要设立;
}
int main() {
	cin >> n >> k;
	int x, y;
	for (int i = 1; i <= n - 1; i++) {
		cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	int l = 1;
	int r = n;
	int ans;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if (ck(mid)) {
			r = mid - 1;
			ans = mid;
		} else {
			l = mid + 1;
		}
	}
	cout << ans;
	return 0;
}

可能把 g 数组设为 INF 以删除子树这种操作也是一种常见套路吧;

C. Ball Collector 0pts

用到了一种数据结构,叫可撤销并查集;

其实这种题又有一种常见套路:将 aibi 连边;

其实赛时想出来了,不过是将 aibi 的反面连边,然后跑 2SAT,结果最后发现只能输出一种可行方案,不能输出最大值,这些专题确实赶得太紧了,没掌握

连完边后,我们会得到若干个连通块,每个连通块有如下两个形态:

  1. 一棵树;

  2. 不是一棵树;

我们发现,只要一个点能够对应上独立的一条边,那么这个点就能选;

所以第二种情况能够选所有的边,要对答案产生贡献,只需研究第一种情况;

显然,我们让每条边对应一个儿子节点,能够匹配的点数最多;

注意,可撤销并查集只能用按秩合并,不能压缩路径,因为这样会破坏树的结构;

合并时,类似启发式合并,我们将 size 小的节点连到 size 大的节点的下面;

所以我们每次维护可撤销并查集,只需判断要连的两个点是否在同一个连通块内,若在,则判断这个连通块内深度最小的那个点是否被选,若被选(第二种情况),则不用考虑,继续搜;若没有被选(第一种情况),则标记其被选,并统计答案;若不在,则将 size 小的那个点标记为被选,同时若以前两个点中有一个点被选,则都可以选;

至于撤销的操作,其实就类似我们打暴搜时回溯取消标记一样,回溯时直接将所有改动的数组及变量复原即可;

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[1000005], b[1000005];
struct sas{
	int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
}
int fa[1000005], siz[1000005];
int num;
bool vis[1000005];
int ans[1000005];
int find(int x) {
	return (x == fa[x]) ? x : find(fa[x]);
}
void dfs(int x, int faa) {
	ans[x] = num;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == faa) continue;
		int x1 = a[u];
		int y = b[u];
		x1 = find(x1);
		y = find(y);
		if (siz[x1] < siz[y]) swap(x1, y);
		if (x1 == y) { // 在一个连通块里;
			if (!vis[x1]) {
				vis[x1] = true;
				num++;
				dfs(u, x);
				vis[x1] = false; //回溯时取消更改;
				num--;
			} else {
				dfs(u, x);
			}
		} else { // 不在一个连通块里;
			int s = siz[x1];
			bool xx = vis[x1];
			bool yy = vis[y];
			fa[y] = x1;
			vis[y] = true;
			vis[x1] = xx | yy;
			siz[x1] += siz[y];
			if (!xx || !yy) num++;
			dfs(u, x);
			fa[y] = y; // 回溯时取消更改;
			siz[x1] = s;
			vis[x1] = xx;
			vis[y] = yy;
			if (!xx || !yy) num--;
		}
	}
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
	}
	int x, y;
	for (int i = 1; i <= n - 1; i++) {
		cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
		siz[i] = 1;
	}
	num = 1;
	if (a[1] == b[1]) {
		vis[a[1]] = true;
	} else {
		fa[b[1]] = a[1];
		siz[a[1]] += siz[b[1]];
	}
	dfs(1, 0);
	for (int i = 2; i <= n; i++) cout << ans[i] << ' ';
	return 0;
}

T4没改完,不写了

B. 简单的拉格朗日反演练习题(lagrange)0pts

记一下套路;

将每条边的 id 转化为边权,求一个最小生成树,最后树剖一下求相邻两点间的路径即可;

主要是将每条边的 id 转化为边权这个套路,好像记得以前刚刚接触 OI 时碰见过,其实当时做过很多思维题,但现在几乎全忘了,也不知道是不是脑子不好使了反正就挺难受;

B. 速度型高松灯

原题:Luogu P3216 [HNOI2011] 数学作业

记一下套路;

这题的递推很好写,主要是加速过程;

用矩阵快速幂可以加速线性转移;

把转移矩阵写出来后,给它快速幂即可;

赛时忘了矩阵快速幂还可以干这事,其实如果想起来也忘了要咋打

点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
unsigned long long nn, mm;
struct Mat{
	unsigned long long n, m;
	unsigned long long a[105][105];
	void zero() {
		memset(a, 0, sizeof(a));
	}
	void rsize(unsigned long long a, unsigned long long b) {
		n = a;
		m = b;
	}
	void one() {
		zero();
		for (unsigned long long i = 1; i <= n; i++) a[i][i] = 1;
	}
	Mat operator *(const Mat &A) const {
		Mat ans;
		ans.zero();
		ans.rsize(n, A.m);
		for (unsigned long long i = 1; i <= n; i++) {
			for (unsigned long long j = 1; j <= A.m; j++) {
				for (unsigned long long k = 1; k <= m; k++) {
					ans.a[i][j] = (ans.a[i][j] % mm + (unsigned long long)a[i][k] * A.a[k][j] % mm) % mm;
				}
			}
		}
		return ans;
	}
};
Mat ksm(Mat A, unsigned long long b) {
	Mat ans;
	ans.rsize(3, 3);
	ans.one();
	while(b) {
		if (b & 1) ans = ans * A;
		A = A * A;
		b >>= 1;
	}
	return ans;
}
unsigned long long p[105];
unsigned long long cnt;
int main() {
	cin >> nn >> mm;
	Mat B;
	B.rsize(3, 3);
	B.zero();
	Mat A;
	A.rsize(3, 3);
	A.zero();
	A.one();
	p[0] = 1;
	for (unsigned long long i = 1; i <= 18; i++) {
		p[i] = p[i - 1] * 10ll;
	}
	unsigned long long s = nn;
	while(s) {
		s /= 10;
		cnt++;
	}
	for (unsigned long long k = 1; k <= cnt; k++) {
		B.zero();
		B.a[1][1] = p[k] % mm;
		B.a[1][2] = B.a[2][2] = B.a[2][3] = B.a[3][3] = 1;
		if (k == 1) {
			A = A * ksm(B, min(nn - p[k - 1], p[k] - p[k - 1] - 1));
		} else {
			A = ksm(B, min(nn - p[k - 1] + 1, p[k] - p[k - 1])) * A;
		}
	}
	cout << (A.a[1][1] + 2ll * A.a[1][2] + A.a[1][3]) % mm;
	return 0;
}
posted @   Peppa_Even_Pig  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库
· 数据并发安全校验处理工具类
点击右上角即可分享
微信分享提示