科大国创杯初中组 2025

背景:

最近,我同学参加了这个比赛,个人因为whk不好,没去参加,感觉有点可惜,觉得今年科大的题真是一年难于一年呀!(对于 T3,T4 而言),接下来请让我们来看看这次比赛的题目难度如何吧!

解题:

T1:前置知识:模拟

题目原文:

题目描述

小可可正在研究 W 市的足球联赛。

W 市的足球联赛有 n 支球队,球队编号分别为 1,2,3…n,每个赛季,球队之间会进行比赛。

已知本赛季进行了 m 场比赛,第 i 场是 ai​,bi​ 两支球队进行比赛,并且比分是 ci​:di​,根据比分判定胜负,从而影响积分。

  1. 如果 ci​>di​,则 ai​ 球队获胜。
  2. 如果 ci​<di​,则 bi​ 球队获胜。
  3. 如果 ci​=di​,则 ai​ 和 bi​ 球队平局。

赛后积分变化:每场比赛如果分出胜负,则胜者得 3 分,败者不得分;否则平局双方各得 1 分。

需要求出 m 场比赛后每支球队的积分。

思路:模拟以上过程即可,代码仅供参考:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, m, a[10001], b[10001], c[10001], d[10001];
map<int, int>mp;

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) cin >> a[i] >> b[i] >> c[i] >> d[i];
	for (int i = 1; i <= m; i++) {
		if (c[i] > d[i]) mp[a[i]] += 3;
		else if (c[i] < d[i]) mp[b[i]] += 3;
		else mp[a[i]] += 1, mp[b[i]] += 1;
	}
	for (int i = 1; i <= n; i++) cout << mp[i] << " ";
	return 0;
}

T2:前置知识:贪心

题目原文:

题目描述

小可可买了美味的果汁,但他太懒了,于是要求奶龙给他倒果汁。

有 m 个水杯,第 i 个水杯容量为 Vi​。现在有 n 个单位体积的果汁,小可可要求奶龙一共倒 n 次(每次倒一个单位体积,正好倒完),第 j 次倒果汁只能倒进第 aj​ 或 aj​+1 个水杯。具体的,如果第 aj​ 和 aj​+1 两个水杯都不是满的,那么可以任意倒入其中一个;如果有且仅有一个不是满的,那么只能倒入不是满的的那一个;如果都已经被倒满了,那么小奶龙就可以开心地喝掉这一个单位体积的果汁。并且为了方便,小可可会安排小奶龙从左向右倒果汁,也就是说对于所有 1≤j<n,保证 aj​≤aj+1​。

小奶龙想知道自己最多能喝掉单位体积的果汁,你能帮帮他吗?

你一共需要解决 T 组测试数据。

思路:

首先读完题,结合样例解释后,一眼贪心,我想了10分钟,想到了一种大致的贪心策略:如果第x杯与第(x+1)杯都没有装满果汁,将1各单位的果汁装进第(x+1)杯中;如果第 x+1 杯被装满了,装第 x 杯中 ;如果第 x 杯被装满了,装第 x+1 杯中 ;否则,只能喝一个单位的果汁了。

证明过程:因为题目说了 a 数组是一个单调递增的,所以如果我们倒第x 杯中,那么后面一个的就到不了,这样果汁就会分散在一些杯子中,所以,必定不划算;反之,果汁就会集中在一些靠后的杯子中,到了后期,必定有一些果汁是倒不了,此时答案才能是最大化的。

代码实验不难,以下代码仅供参考:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int t;
int n, m;
int v[100001];
int a[1000001];
int ans;
signed main() {
//	freopen("juice2.in","r",stdin);
//	freopen("juice2.out","w",stdout);
	cin >> t;
	while (t--) {
		ans = 0;
		cin >> n >> m;
		for (int i = 1; i <= m; i++) cin >> v[i];
		for (int i = 1; i <= n; i++) cin >> a[i];
		for (int i = 1; i <= n; i++) {
//			cout << v[a[i]] << " " << v[a[i] + 1] << endl;
			if (v[a[i]] && v[a[i] + 1]) v[a[i] + 1]--;
			else if(!v[a[i]] && v[a[i] + 1]) v[a[i] + 1] --;
			else if (v[a[i]] && !v[a[i] + 1]) v[a[i]]--;
			else ans++;
		}
		cout << ans << endl;
	}
	return 0;
}

T3:前置知识:拓扑排序,bitset

如果不了解bitset,可以在oi-wiki看。

声明:此题中的或运算用 or 表示

题目原文:

题目描述

小可可来到了 P 国旅行。

P 国共有 n 个城市和 m 条有向道路,其中第 i 条道路为从城市 ui​ 到城市 vi​,长度为 wi​。保证 ui​<vi​。

对于一次游览,假设小可可依次经过了城市 x1​,x2​,…,xk​,其中从 xi​ 到 xi+1​ 经过的路径长度为 yi​。记 si​ 表示 y1​,y2​,…,yi​ 的按位或值。那么小可可认为这次游览就是从 x1​ 走到 xk​,疲劳度就是 mex(s1​,s2​,…,sk−1​)。

其中 mex(a1​,a2​,…,an​) 表示最小的没有出现在 a1​,a2​,…,an​ 中的自然数。例如 mex(0,2,3)=1,mex(1,4)=0,mex(0,1,2,3,4)=5。

小可可认为两座城市 s,t 之间的距离为他从 s 走到 t 所有可能的游览方案中疲劳度的最大值,记作 d(s,t)。如果不能从 s 走到 t,那么 d(s,t)=−1。规定 d(s,s)=0。现在他想要知道任意两座城市之间的距离之和,即 $$\displaystyle \sum_{i=1}^{n} \sum_{j=1}^{n} d(i, j)$$

思路:

首先我们能发现一些性质:

命题:这题的 mex 值只能为 0,1,2。

证明:如果 mex>2,那么 mex 一定有 0,1,2 的前缀,但是有 2 的一项一定是从 1 or 过来的,1 or x = 2,无解,因为无论x取什么值,1 or x 只能等于 1 ,故原命题是真命题。

3 种情况:

1:从某个点出发,第一条边边权不是 0。那么后面不管怎么走,得到的 mex 组成的序列单调不降,第一条边的边权就是最小值,序列中不可能出现 0,所以 mex 是 0。

2:从某个点出发,第一条边边权是 0,第一条不是 0 的边边权不是 1,或这条链上根本没有边权不是 0 的边。同情况 1,此时序列中不可能出现 1,mex 是 1。

3:如果从某个点出发,第一条边边权是 0,第一条不是 0 的边边权是 1,那么显然疲劳度序列中要么是 0,要么由于按位或 1 而是偶数,mex 为 2。

而题目给的 d(s,t)函数分如下情况:

1:d(s,t)=-1 时,我们可以在原图的基础上先存一个反边,然后用拓扑排序求出每个点可以到达哪些点,这一步骤使用 bitset 将很好实现。这样,我们就可以统计出有多少条无法到达的路径,这样的路径的疲劳度是 −1。

2:d(s,t)=0 时,对答案无贡献,不计算。

3:d(s,t)=1 时,

这个很好理解。对于一个点 u,如果有一条通向点 v 的边且边权为 0,那么 u 与 v 所有可以到达的点的路径疲劳度都至少为 1,因为这里可能包含着情况 3。

遍历所有的边,对于所有边权为 0 的边进行统计即可。

4:d(s,t)=2 时,首先,统计出每个点通过一条满足第一条边的边权为 1 的路径可以到达的点的数量。然后,统计有每个点只通过边权为 0 的边可以到达的点,假定有一点 u,可以通过一条边权全部为 0 的路径到达点 v,那么 u 与所有满足 v 通过一条满足第一条边的边权为 1 的路径可以到达的点之间的路径疲劳度为 2。

将答案累加输出即可。

代码仅供参考:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int ans;
bitset<30004>get1[30004];//表示从城市 i 可以直接或间接到达的所有城市的集合,使用 bitset 存储,方便进行位运算
vector<pair<int, int>>g[30004];/*是一个邻接表,
/存储从城市 i 出发的所有有向边,pair 中的第一个元素是目标城市,第二个元素是边的长度/*/
bitset<30004>zero[30004]//表示从城市 i 出发,经过长度为 0 的边可以直接到达的城市集合(mex=0)
, one[30004], // 表示从城市 i 出发,经过长度为 1 的边可以直接或间接到达的城市集合(mex=1)
two[30004];//表示从城市 i 出发,经过长度为 2 的边可以直接或间接到达的城市集合(mex=1)

signed main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		get1[u][v] = 1;//标记从城市 u 可以到达城市 v
		if (w == 0) zero[u][v] = 1;//标记从城市 u 经过长度为 0 的边可以到达城市 v
		if (w == 1) one[u][v] = 1;//标记从城市 u 经过长度为 1 的边可以到达城市 v
		g[u].push_back({v,w});//边 (u, v, w) 添加到邻接表 g 中
	}
	ans = 1 - n;//为了在后续计算中更方便、准确地处理自环和不可达情况
	for (int i = n - 1; i >= 1; i--) {//从倒数第二个城市开始,逆序处理每个城市
	 // 遍历从城市 i 出发的所有边
		for (auto it:g[i]) {
			int v = it.first//获取当前边的目标城市 v
			, w = it.second;//获取当前边的长度 w
			get1[i] |= get1[v];
			if (w == 0)// i 与 j 之间的边权为 0
			{
				two[i] |= one[v];
				zero[i] |= get1[v];
				one[i] |= one[v];
			}
			else if (w == 1) 
			one[i] |= get1[v];// 这里统计了每一个点通过一条满足第一条边的边权为 1 的路径可以到达哪些点。
		}
		//减去从 i 无法到达的城市对结果的贡献
		ans -= (n - 1) - get1[i].count();
		ans += two[i].count();
		two[i] |= zero[i];
		ans += two[i].count();
	}
	cout << ans;
	return 0;
}
/*
*/

T4:不会,鸽了。。。

总结:

T3 我觉得出的挺好的,自己 vp 的时候,没有思路,打了暴力,‘’荣获‘’ 25 分,看了题解后,过了很久才理解。T1,T2 还是挺常规的。

VP 总分:100+100+25=225 分,不知道是否能上2+?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值