2024 (ICPC) Jiangxi Provincial Contest -- Official Contest

题目链接:

Dashboard - 2024 (ICPC) Jiangxi Provincial Contest -- Official Contest - Codeforces

A

Maliang Learning Painting

题目大意:

给定a、b、c三个数,求a+b+c

解题思路:

直接加就好了

数据范围:

A (1≤A≤1e4), B (1≤B≤1e4), C (1≤C≤1e4)

代码:

//solve函数
void solve() {
	int a, b, c;
	cin >> a >> b >> c;
	cout << (a + b + c) << "\n";
}
//main 函数 
signed main() {
	std::ios::sync_with_stdio(0);
	std::cout.tie(0);
	std::cin.tie(0);
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

C

Liar

题目大意:

游戏中有 n 名玩家。每个玩家都被分配了一个整数,不同玩家的整数可能不同,这些数字的总和为 s。其中 i个玩家声称分配给他的数字是 ai ,但这一说法可能并不属实。他们中最多有多少人说的是真话?

解题思路:

一个人说谎就能使剩下的人全部说真话,因为说谎者的数字可正可负,所以可以凑出任何数。

所以如果累加和为目标则输出n,否则输出n-1

数据范围:

第一行包含两个整数 n,s ( 1≤n≤1e5,−109≤s≤1e9)。

第二行包含 n个整数 a1,a2,…,an ( −1e4≤ai≤1e4 ),代表玩家声称已分配到的数字。

代码:

void solve() {
	int n,s;
	cin >> n >> s;
	int sum = 0;
	for (int i = 1; i <= n; ++i) {
		int x;
		cin >> x;
		sum += x;
	}
	cout << (sum == s ? n : n - 1) << "\n";
}
//main 函数 
signed main() {
	std::ios::sync_with_stdio(0);
	std::cout.tie(0);
	std::cin.tie(0);
	int t = 1;
	//cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

D

Magic LCM

题目大意:

星光闪烁有一个长度为 n 的魔法数列 v1,v2,v3,…,vn,她可以对这个数列进行以下操作无限次:

  • 选择两个下标 i,j( 1≤i,j≤n ),设置 x←gcd(vi,vj) , y←lcm(vi,vj) ,然后做出 vi←x,vj←y

其中 gcd(x,y)表示两个正整数 x,y 的最大公约数, lcm(x,y) 表示两个正整数 x,y的最小公倍数。现在她想知道运算后这个数列之和的最大值是多少,即 max∑vi,为了避免答案过大,需要对 998244353 进行调制来打印。

解题思路:

首先可以发现对序列的两个数据ai,aj进行操作,必须满足ai与aj不是整除的关系,这样才能有贡献。而且我们可以发现无论操作的顺序如何,直到无法产生贡献以后,答案都是一样的。所以这也启发我们去从lcm和gcd根本来看(无论怎么操作,最终结果都一样,可能是有公式)。可以发现,lcm可以看做是对两个数的公共因子的最大幂次的乘积,gcd可以看做是最小的乘积。那么就很显然了,无法操作的结果一定是最小幂级数和最小的幂级数因子乘在一起,最大的幂级数因子和最大的幂级数因子乘在一起。然后这些数加起来就是最终的答案了。

做法

考虑如何去分解因子,大概率会想到分解素因子,因为每个数的素因子分解是唯一的。朴素的带根号的分解肯定不行,所以我们用筛法线筛掉素数,然后再进行分解,这样即使1e6也能跑。

分解后可以对这个序列进行排序,一种做法是开个桶(因为素数不超过1e6的大小),或者使用map更简单,对每个素数的序列排序,然后让大的幂次和大的幂次进行乘积,最后累加起来就好了,记得要及时删除map,不然会多次重复访问。

总的复杂度在k*nlogn左右,k是最大分解不同的素因子的个数(7以内)。

数据范围:

有多个测试用例。第一行包含一个整数 T ( 1≤T≤1e6),表示每个测试用例的测试用例数:第一行包含一个整数 n( 1≤n≤1e6) 表示魔法序列的长度。第二行包含 n个整数 v1,v2,…,vn ( 1≤vi≤1e6),表示神奇序列。保证所有数据中 n的总和不超过 1e6 。

代码:

bitset<N + 10>is_prime; //表示是否为素数 0表示是,1表示不是
int p[N + 10]; //素数列表
int tot = 0;
void prime() {
	for (int i = 2; i <= N; i++) {
		if (!is_prime[i]) p[++tot] = i;
		for (int j = 1; p[j] * i <= N; j++) {
			is_prime[p[j] * i] = 1; //筛选出非素数 
			if (i % p[j] == 0) break; //减少重复计算
		}
	}
	is_prime[1] = 1;
}
//solve函数
void solve() {
	int n;
	cin >> n;
	vector<int>a(n + 1);
	map<int, vector<int>>mp;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	//分解质因子和幂次
	for (int i = 1; i <= n; ++i) {
		int x = a[i];
		for (int i = 1; i <= tot; ++i) {
			if (p[i] * p[i] > x) {
				break;
			}
			int cnt = 0;
			while (x % p[i] == 0) {
				x /= p[i];
				++cnt;
			}
			if (cnt != 0) {
				mp[p[i]].push_back(cnt);
			}
		}
		if (x > 1) {
			mp[x].push_back(1);
		}
 
	}
	int len = 0;
	for (auto& x : mp) {
		auto& vec = x.second;
		len = max(len, (ll)vec.size());
		sort(vec.begin(), vec.end());
	}
	int k = 1;
	int res = 0;
	for (int i = 0; i < len; ++i) {
		int t = 1;
		for (auto it = mp.begin(); it != mp.end();) {
			auto& vec = it->second;
			int siz = vec.size();
			if (siz - k >= 0) {
				t = (t * ksm(it->first, vec[siz - k])) % mod;
				if (siz - k == 0) {
					mp.erase(it++);
				}
				else {
					++it;
				}
			}
		}
		k++;
		res = (res + t) % mod;
	}
	res = (res + (n - len)) % mod;
	std::cout << res << "\n";
 
}
//main 函数 
signed main() {
	std::ios::sync_with_stdio(0);
	std::cout.tie(0);
	std::cin.tie(0);
	int t = 1;
	prime();
	cin >> t;
	while (t--) {
		solve();
	}
	return 0;
}

 G

Multiples of 5

题目大意:

给定一个长度小于 1014 的非负基数 11 整数,判断它是否是 5的倍数。由于输入量较大,输入以 n

对 (ai,bi) 的形式给出。其中每一对 (ai,bi) 代表 ai 个连续数字,所有数字都是 bi。将所有数字对按顺序连接起来,就得到了输入的数字。这里我们用数字 0、1、2、 …、9 和字母 A 来表示基数 11

的数字系统。例如,输入的 (1,1),(4,5),(1,4)表示数字 (155554)11 。具体来说,十进制数 110 相当于输入的 (1,A),(1,0) 。

解题思路:

朴素的想法是把十一进制转为十进制然后判断是否可以被5整除。但是这里太大了,哪怕是遍历一次都会超时。所以考虑找规律,发现11的任何次幂都是以1结尾的,而5的倍数就是以0或者5结尾的,所以贡献就是x*y,其中x是给定的个数,y是给定的数字,注意还要特判一下A,因为A(10)是没有贡献的

数据范围:

第一行包含一个整数 T ( 1≤T≤103 )。 1≤T≤103),代表测试用例的数量。对于每个测试用例第一行包含一个正整数 n( 1≤n≤105)。接下来的 n行分别包含一个正整数 ai ( 1≤ai≤109 ) 和一个字符 bi ( bi∈{0,1,2,…,9,A} ),代表 i对。保证所有测试用例的 n之和不超过 105。请注意,该数据并不***保证没有前导零。

代码:

void solve() {
	int n;
	cin >> n;
	int sum = 0;
	for (int i = 1; i <= n; ++i) {
		int x;
		char y;
		cin >> x >> y;
		if (y == 'A') {
			continue;
		}
		else {
			sum += x * (y - '0');
		}
		sum %= 5;
	}
	cout << ((sum % 5 == 0) ? "Yes" : "No") << "\n";
}

H

Convolution

题目大意

给一个矩阵,让你构造一个矩阵,然后通过O(p, q) = ∑kx=1∑ly=1 K(x, y) × I(p + x − 1, q + y − 1)运算求出每个点,求和后的最大值,构造的句子数字只能是1、0、-1

解题思路:

读题比较累,读完发现就是要求一个子矩阵的和,暴力求和肯定超时,使用二维前缀和可以很快的算出来,接着就让子矩阵和为负数的*-1,正数的*1,然后加起来就好了。

数据范围:

n (1 ≤ n ≤ 1e3), m (1 ≤ m ≤ 1e3), k (1 ≤ k ≤ n), l (1 ≤ l ≤ m).Ii,j (−1e6 ≤ Ii,j ≤ 1e6)

代码:

const int N = 3e3 + 10;
int a[N][N];
void solve() {
	int n, m, p,q;
	cin >> n >> m >> p >> q;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			cin >> a[i][j];
		}
	}
	//二维前缀和
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
		}
	}
	//求贡献
	int res = 0;
	for (int i = 1; i <= p; ++i) {
		for (int j = 1; j <= q; ++j) {
			int x = n - p + i, y = m - q + j;
			res += abs(a[x][y] - a[i - 1][y] - a[x][j - 1] + a[i - 1][j - 1]);
		}
	}
	cout << res << "\n";
}

J

Magic Mahjong

题目大意:

也是一道读题比较累的题目,可能江西人比较懂这个麻将

就是判断给定的牌型是不是十三幺或者对对子,输出相应答案就好了

解题思路:

排个序,或者用map存一下就好了

数据范围:

长度为14的字符串,不超过1e4组

代码:

void solve() {
	string s;
	cin >> s;
	map<pair<char,char>, int>mp;
	for (int i = 0;i<s.size();i+=2) {
		mp[{s[i], s[i + 1]}]++;
	}
	string t1 = "spm";
	bool flag = true;
	for (int i = 0; i < 3; ++i) {
		if (!mp.count({ '1',t1[i]}) || !mp.count({'9', t1[i]})) {
			flag = false;
			break;
		}
	}
	for (int i = 1; i <= 7; i++) {
		if (!mp.count({ '0' + i,'z' })) {
			flag = false;
			break;
		}
	}
	if (flag) {
		cout << "Thirteen Orphans\n";
		return;
	}
	flag = true;
	for (auto& x : mp) {
		if (x.second != 2) {
			flag = false;
			break;
		}
	}
	if (flag) {
		cout << "7 Pairs\n";
	}
	else {
		cout << "Otherwise\n";
	}
}

K

Magic Tree

题目大意:

tarlight Glimmer 的网格为 2 /行, m /列,其中 i 行和 j 列标记为 (i,j)。她从标记为 (1,1)的网格(即第一行和第一列)开始执行深度优先搜索,每个网格都能到达与其至少一条边相邻的网格。由于搜索过程不会重复访问网格,因此可以用一棵树来描述搜索过程。现在她想知道标记树的可能结果一共有多少个,为了避免答案太大,需要用 998244353求模来打印。

解题思路:

模拟dfs打个表或者手动模拟一下就发现答案是2^(n-1)了,也可以理解为每次都多两个,也就是*2的方案,有点动态规划转移方案的感觉

数据范围:

第一行包含一个整数 m ( 1≤m≤105 ),表示网格的列数。

代码:

void solve() {
	int n;
	cin >> n;
	int res = 1;
	for (int i = 1; i <= n-1; ++i) {
		res = (res << 1) % mod;
	}
	cout << res << "\n";
}

L

Campus

题目大意:

在樱花盛开的季节,西湖大学吸引了大量游客,这让胥胥非常烦恼。于是,他发明了一个神奇的按钮,按下按钮后,校园里所有的游客都会以光速从最近的大门离开学校。现在,胥胥非常好奇,游客们以光速离开学校时,每时每刻所走的距离总和是多少。具体来说,WHU 是一个无向图,有 n个节点和 m 条边。每个节点都有 ai 名游客。在 n 个节点中, k 个节点作为大门,每个大门的开启时间间隔为 [li,ri] 。问题是,从 1 到 T的每个时刻,游客以光速离开校园的距离总和是多少?注: 如果有游客无法离开学校,则距离之和应假设为 −1。保证给定数据的图形连通、无自循环和存在多条边。

解题思路:

可以发现是一个多源最短路径,只有最多15个门,所以跑15次dijkstra就好。至于如何再哪一些门开的时候,最短路径是什么,可以提前算一下,因为最多15个门,集合大概是3e4个, 预处理一下每个可能的情况对应的最短路径,就可以最后算出来了,还得预处理一下每个时间点开的门有哪些一些

做法:

一种做法是跑k次dijkstra后,使用map<vector<int>,int>来存一下每种门开启的组合的最短路径之和,也可以使用字典树存这个(会少一个log),当然使用STL更简单写一点,预处理时间点开启的门直接遍历就好了,因为k<=15

数据范围:

第一行包含 n ( 1≤n≤105 ), m ( 1≤m≤105 ), k ( 1≤k≤15 ), T ( 1≤T≤105 ) 四个整数。( 1≤T≤105).第二行包含 n个整数 ai ( 1≤ai≤103 ),代表 i-节点的游客数量。接下来的 k行中,每一行都包含三个整数 pi ( 1≤pi≤n )。( 1≤pi≤n ), li , ri ( 1≤li≤ri≤T ),代表 i /th 门是图中索引为 pi 的节点,门的开启时间为 [li,ri]。接下来的每行 m都包含三个整数 u,v,w ( 1≤u,v≤n,1≤w≤103 、 1≤u,v≤n,1≤w≤103 、 1≤u,v≤n,1≤w≤103 )。 1≤u,v≤n,1≤w≤103 ),代表 u 和 v 之间长度为 w 的路径。

代码:

const int N = 1e5 + 10;
struct node { //开门的时间
	int idx;
	int l, r;
};
struct edge {
	int v, dis; //终点,边权
	bool operator<(const edge& x) const { //重构<符号
		return x.dis < dis;
	}
};
int dis[20][N];//起点距离每个点的距离
int vis[20][N];
int n, m, k, t;
map<vector<int>, int>mp;
vector<edge>e[N];
vector<int>cnt[N];
node b[N];
int a[N];
/* Djkstra单源最短路径算法 */
void dijkstra(int s) {
	priority_queue<edge>pq; //利用到STL优先队列
	for (int i = 1; i <= n; i++) {
		dis[s][i] = inf; //初始化无穷大
		vis[s][i] = 0; //记录点是否在优先队列中
	}
	pq.push({ b[s].idx,0 }); dis[s][b[s].idx] = 0; //初始化
	while (!pq.empty()) {
		int u = pq.top().v; pq.pop();//取出队头
		if (vis[s][u]) continue;
		vis[s][u] = 1;
		for (auto& x : e[u]) { //遍历图
			int v = x.v;
			if (dis[s][u] + x.dis < dis[s][v]) { //更新距离
				dis[s][v] = dis[s][u] + x.dis;
				if (vis[s][v] == 0) { //没入队则入队
					pq.push({ v,dis[s][v] });
				}
			}
		}
	}
}
void solve() {
	cin >> n >> m >> k >> t;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= k; ++i) {
		cin >> b[i].idx >> b[i].l >> b[i].r;
	}
	for (int i = 1; i <= m; ++i) {
		int u, v, w;
		cin >> u >> v >> w;
		e[u].push_back({ v, w });
		e[v].push_back({ u, w });
	}
	for (int i = 1; i <= k; ++i) {
		dijkstra(i);
	}
	for (int i = 1; i <= t; ++i) {
		for (int j = 1; j <= k; ++j) { //枚举每个门的开放时间
			if (b[j].l <= i && b[j].r >= i) {
				cnt[i].push_back(j);
			}
		}
		mp.insert({ cnt[i], 0 });
	}
	//预处理每个门最近的学生数量*距离
	vector<int>w(k + 1);
	for (auto& vec : mp) {
		for (int i = 1; i <= n; ++i) {
			int minx = inf;
			int idx = 0;
			vector<int>tmp = vec.first;
			for (auto& x : tmp) {
				minx = min(minx, dis[x][i] * a[i]);
			}
			vec.second += minx;
		}
	}
	for (int i = 1; i <= t; ++i) {
		int res = 0;
		if (cnt[i].empty()) {
			cout << -1 << "\n";
			continue;
		}
		cout << mp[cnt[i]] << "\n";
	}
 
}

 

 

 

 

 

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值