CSP-S 2022 总结

比赛期间

考前有二十多分钟准备时间,我中途上了个厕所,再弄弄jsoi,也就十分钟了。代码模板先前没打熟练。题目2:25发下来,我还在打模板,打到2:30才看题目

T1 假期计划

暴力之前

我的心态是,“T1,应该能AC吧”,想了一会知道暴力怎么打了,但是半个多小时之后才动手,因为在尝试想出标算
还看了一眼T2,看看哪个更好拿分。但是被T2吓到了,回来打T1的暴力。

暴力 - 75pts TLE

k=0时,很简单,找出一个长度为5的环,点的权值和最大。容易写出暴力。
我先拿了这一部分的分。

当k>0,可以先bfs,建一个新图,然后跟上面一样。
在上面的基础上,我改了改程序。
时间复杂度 O ( n 4 ) O(n^4) O(n4)

当时分了两次写。其实完全可以一次性把程序打出来,毕竟很简单。

int n, m, k, dis[MAXN];
ll w[MAXN], ans;
bool vh[MAXN], vis[MAXN];
vector<int> g[MAXN], ng[MAXN];

void setIO(string name) {
	freopen((name + ".in").c_str(), "r", stdin);
	freopen((name + ".out").c_str(), "w", stdout);
}

void build(int beg) {
	memset(dis, 0x3f, sizeof(dis)); dis[beg] = 0;
	memset(vis, 0, sizeof(vis)); vis[beg] = true;
	queue<int> q; q.push(beg);
	while (!q.empty()) {
		int u = q.front(); q.pop();
		if (dis[u] > k) break;
		for (int i = 0; i < g[u].size(); ++i) {
			int v = g[u][i];
			if (!vis[v] && dis[v] > dis[u] + 1) {
				dis[v] = dis[u] + 1;
				vis[v] = true;
				ng[beg].push_back(v);
				q.push(v);
			}
		}
	}
}

void dfs_k0(int u, int p, ll s) {
	if (p == 4) {
		if (vh[u]) ans = max(ans, s);
		return;
	}
	for (int i = 0; i < ng[u].size(); ++i) {
		int v = ng[u][i];
		if (!vis[v] && v > 1) {
			vis[v] = true;
			dfs_k0(v, p + 1, s + w[v]);
			vis[v] = false;
		}
	}
}

int main() {
	
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 2; i <= n; ++i) {
		scanf("%lld", w + i);
	}
	for (int i = 1; i <= m; ++i) {
		int x, y; scanf("%d%d", &x, &y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	
	for (int i = 1; i <= n; ++i) {
		build(i);
	}
	
	for (int i = 0; i < ng[1].size(); ++i) {
		vh[ng[1][i]] = true;
	}
	memset(vis, 0, sizeof(vis));
	dfs_k0(1, 0, 0);
	
	
//	if (k == 0) {
//		dfs_k0(1, 0, 0);
//	}

	printf("%lld\n", ans);
	
	fclose(stdin); fclose(stdout);
	return 0;
}

进一步思考

数据量2500,是不是 O ( n 2 ) O(n^2) O(n2)或者 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
想到先枚举和起点相连的两个景点,即景点1、4。有没有什么方法,快速找出中间两个节点,使权值和最大?
这是个变数很多的问题。好难啊。

继续想——约90min就过去了。

T2 策略游戏

我甚至连暴力都写不出来

游戏中,小L和小Q同时选择一个下标……

读到这句话,我就意识到这题不简单。
更加地,样例2,没有说明。我甚至不知道样例2为什么要输出那些答案!

csp,T2,考博弈论?大纲里不是没有吗?

难道要算选择某一个下标的期望得分吗?可是,小L选择了某一行,小Q选择各列,并不是等可能性的,因为小Q也有智慧。甚至对于不同的行,小Q选择各列的可能性也不同。
小L预判小Q,小Q预判小L……这怎么搞?

一分都拿不到。去看T3,T4。

T4 数据传输

读了T3,T4,感觉T4更好拿分,先打T4。

针对k=1)LCA - 16pts TLE

如题。

void predfs(int u, int par) {
	dep[u] = dep[par] + 1;
	fa[u][0] = par;
	for (int j = 1; j < MAXP; ++j) {
		fa[u][j] = fa[fa[u][j - 1]][j - 1];
	}
	dis[u] = dis[par] + w[u];
	for (int i = 0; i < g[u].size(); ++i) {
		int v = g[u][i];
		if (v == par) continue;
		predfs(v, u);
	}
}
int lca(int x, int y) {
	if (dep[x] < dep[y]) swap(x, y);
	int d = dep[x] - dep[y];
	for (int i = 0; i < MAXP; ++i) {
		if (d & (1 << i)) x = fa[x][i];
	}
	if (x == y) return x;
	for (int i = MAXP - 1; i >= 0; --i) {
		if (fa[x][i] != fa[y][i]) {
			x = fa[x][i]; y = fa[y][i];
		}
	}
	return fa[x][0];
}

int main() {
	scanf("%d%d%d", &n, &q, &k);
	for (int i = 1; i <= n; ++i) {
		scanf("%lld", w + i);
	}
	for (int i = 1; i < n; ++i) {
		int a, b; scanf("%d%d", &a, &b);
		g[a].push_back(b); g[b].push_back(a);
	}
	
	predfs(1, 0);
	while (q--) {
		int s, t; scanf("%d%d", &s, &t);
		if (k == 1) {
			int z = lca(s, t);
			printf("%lld\n", dis[s] + dis[t] - dis[z] - dis[fa[z][0]]);
		}
	}
	
	fclose(stdin); fclose(stdout);
	return 0;
}

在路径上dp - 44pts TLE & WA

对于k=1,相当于两个点的路径,一个一个走,每个点都得走。
对于k>1,相当于两个点的路径,跳着走。可以把这条路径找到,然后线性dp。

这个算法不具正确性。当时没有想到这种情况:
在这里插入图片描述
如图,黄色点并不在s到t的简单路径上,但我们可以经过它。
仍然得了44pts,挺好。

int n, q, k, dep[MAXN], fa[MAXN][MAXP];
ll w[MAXN], dis[MAXN], dp[MAXNN];
vector<int> g[MAXN], ax;

void predfs(int u, int par) {
	dep[u] = dep[par] + 1;
	fa[u][0] = par;
	for (int j = 1; j < MAXP; ++j) {
		fa[u][j] = fa[fa[u][j - 1]][j - 1];
	}
	dis[u] = dis[par] + w[u];
	for (int i = 0; i < g[u].size(); ++i) {
		int v = g[u][i];
		if (v == par) continue;
		predfs(v, u);
	}
}
int lca(int x, int y) {
	if (dep[x] < dep[y]) swap(x, y);
	int d = dep[x] - dep[y];
	for (int i = 0; i < MAXP; ++i) {
		if (d & (1 << i)) x = fa[x][i];
	}
	if (x == y) return x;
	for (int i = MAXP - 1; i >= 0; --i) {
		if (fa[x][i] != fa[y][i]) {
			x = fa[x][i]; y = fa[y][i];
		}
	}
	return fa[x][0];
}

void set_arr(int x, int y) {
	vector<int>().swap(ax);
	stack<int> ay;
	if (dep[x] < dep[y]) swap(x, y);
	while (dep[x] > dep[y]) {
		ax.push_back(x);
		x = fa[x][0];
	}
	while (x != y) {
		ax.push_back(x); ay.push(y);
		x = fa[x][0]; y = fa[y][0];
	}
	ax.push_back(x);
	while (!ay.empty()) {
		ax.push_back(ay.top());
		ay.pop();
	}
}


int main() {
	scanf("%d%d%d", &n, &q, &k);
	for (int i = 1; i <= n; ++i) {
		scanf("%lld", w + i);
	}
	for (int i = 1; i < n; ++i) {
		int a, b; scanf("%d%d", &a, &b);
		g[a].push_back(b); g[b].push_back(a);
	}
	
	predfs(1, 0);
	while (q--) {
		int s, t; scanf("%d%d", &s, &t);
		if (k == 1) {
			int z = lca(s, t);
			printf("%lld\n", dis[s] + dis[t] - dis[z] - dis[fa[z][0]]);
		}
		else {
			set_arr(s, t);
			memset(dp, 0x3f, sizeof(dp)); dp[0] = w[ax[0]];
			for (int i = 1; i < k && i < ax.size(); ++i) {
				dp[i] = dp[0] + w[ax[i]];
			}
			for (int i = k; i < ax.size(); ++i) {
				for (int j = 1; j <= k; ++j) {
					dp[i] = min(dp[i], dp[i - j] + w[ax[i]]);
				}
			}
			printf("%lld\n", dp[ax.size() - 1]);
		}
	}
	
	fclose(stdin); fclose(stdout);
	return 0;
}

T3 星战

暴力 - 40pts TLE

经过二十多分钟的读题分析,只需所有点的出度都是1.
容易写出一个 O ( n 2 ) O(n^2) O(n2)的暴力,用邻接矩阵。

当时打错了几个字母,花了一些时间调试

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;

const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 1005;

int mod(int x, int y) { return ((x % y) + y) % y; }
int mod(int x) { return ((x % MOD) + MOD) % MOD; }
ll mod(ll x) { return ((x % MOD) + MOD) % MOD; }
void plusmod(int &x) { x = mod(x); }
void plusmod(ll &x) { x = mod(x); }

void setIO(string name) {
	freopen((name + ".in").c_str(), "r", stdin);
	freopen((name + ".out").c_str(), "w", stdout);
}

int n, m, q, out[MAXN], g[MAXN][MAXN];

bool check() {
	for (int i = 1; i <= n; ++i) {
		if (out[i] != 1) return false;
	}
	return true;
}

void work_matrix() {
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		g[u][v] = 1; ++out[u];
	}
	
	scanf("%d", &q);
	while (q--) {
		int t, u, v; scanf("%d%d", &t, &u);
		if (t & 1) scanf("%d", &v);
		if (t == 1) {
			g[u][v] = 0; --out[u];
		}
		else if (t == 2) {
			for (int i = 1; i <= n; ++i) {
				if (g[i][u] == 1) {
					g[i][u] = 0; --out[i];
				}
			}
		}
		else if (t == 3) {
			g[u][v] = 1; ++out[u];
		}
		else {
			for (int i = 1; i <= n; ++i) {
				if (!g[i][u]) {
					g[i][u] = 1; ++out[i];
				}
			}
		}
		printf(check() ? "YES\n" : "NO\n");
	}
}

int main() {
	memset(g, -1, sizeof(g));
	scanf("%d%d", &n, &m);
	
	work_matrix();
	
	fclose(stdin); fclose(stdout);
	return 0;
}

针对t是1或3) - 40 pts TLE & RE

t只能是1或3时,每次 O ( 1 ) O(1) O(1),过程中简单地维护一个玩意,就可以每次 O ( 1 ) O(1) O(1)地检测。

这个部分分太好拿了,对吧!

  • 这个代码在上面的代码的基础上修改而来。上面的代码,MAXN=1005。我忘记把数组开大了。所以写了半天代码,p用没有,RE。
  • 时间复杂度也没算好。那个把检测变成 O ( 1 ) O(1) O(1)的玩意,当时没有写,以为不写能过。

另外,在拿了前50pts的情况下,后50pts怎么办?我放弃了。实际上,我应当对于后50pts全部输出No,毕竟随便走出一步,总比一步都不走强
另外,当时我有两个选择:继续拿这仅仅10pts的部分分,或者回去想还有100pts没拿的T2。很显然,我理应选择后者

int n, m, q, out[MAXN], g[MAXN][MAXN];

bool check() {
	for (int i = 1; i <= n; ++i) {
		if (out[i] != 1) return false;
	}
	return true;
}

void work_matrix() {
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		g[u][v] = 1; ++out[u];
	}
	
	scanf("%d", &q);
	while (q--) {
		int t, u, v; scanf("%d%d", &t, &u);
		if (t & 1) scanf("%d", &v);
		if (t == 1) {
			g[u][v] = 0; --out[u];
		}
		else if (t == 2) {
			for (int i = 1; i <= n; ++i) {
				if (g[i][u] == 1) {
					g[i][u] = 0; --out[i];
				}
			}
		}
		else if (t == 3) {
			g[u][v] = 1; ++out[u];
		}
		else {
			for (int i = 1; i <= n; ++i) {
				if (!g[i][u]) {
					g[i][u] = 1; ++out[i];
				}
			}
		}
		printf(check() ? "YES\n" : "NO\n");
	}
}

void work_no24() {
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		++out[u];
	}
	
	scanf("%d", &q);
	while (q--) {
		int t, u, v; scanf("%d%d%d", &t, &u, &v);
		if (t == 1) --out[u];
		else ++out[u];
		printf(check() ? "YES\n" : "NO\n");
	}
}

int main() {
	
	memset(g, -1, sizeof(g));
	scanf("%d%d", &n, &m);
	
	if (n <= 1000) work_matrix();
	else work_no24();//结果上来说,是无用的
	
	fclose(stdin); fclose(stdout);
	return 0;
}

T2 策略游戏

又回到了这里。此时,还剩20min。

我依旧毫无思路。

剩下的10min,我先把T2全部输出0,然后看了看另外三道题,我有没有文件操作打错,没开long long之类的错误。很快,时间就到了。
T2,大抵是爆0了。

真是一个策略游戏。

赛后

待完成。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CSP-J2022 是中国计算机学会(China Computer Federation, CCF)主办的全国计算机能力竞赛,是一项面向大学生的计算机科学竞赛。初赛题目以PDF格式发布,PDF是一种跨平台可移植文档格式,可以在不同操作系统和设备上进行查看和打印。初赛PDF包含了竞赛的相关信息,包括竞赛时间、地点、报名要求、参赛资料、题目要求和提交方式等。 CSP-J2022初赛PDF首先会介绍竞赛的背景和目的,对参赛者进行必要的说明。接下来会列出竞赛的各个环节和时间安排,包括笔试、机试等。参赛者需要根据PDF中的要求准备相应的资料,并按照规定的时间和方式提交。 竞赛题目通常会涉及到计算机科学的基础知识和编程能力。PDF中会给出具体的题目要求和答题格式,并提供样例输入输出供参赛者练习。参赛者需要下载并阅读PDF,理解题目要求,并根据要求进行编程实现。提交答案时,需要按照PDF中的指示提交到指定的在线评测系统中进行自动评测。 CSP-J2022 初赛PDF的发布是为了方便参赛者获取竞赛信息和题目要求,帮助参赛者做好充分的准备。参赛者需要仔细阅读PDF,理解竞赛的要求,合理安排学习时间,充分发挥自己的编程能力和思维能力。通过认真准备和实际操作,参赛者有机会在CSP-J2022竞赛中获得优异的成绩,并获得相关的荣誉和奖励。这也有助于提高参赛者的计算机技术水平和解决问题的能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值