题目链接:
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";
}
}