Loading

8.3 团队赛

SDKD 2023 Summer Team Contest A 训练赛总结

2023-08-03 13:00 - 18:00


比赛过程

比赛时我们的题目安排是甲负责前4个, 乙负责中间5个, 丙负责后四个; 因为E和G两个签到题都在中间5个里, 所以乙就很快找出了两个签到题和一个有思路的题并做了标记; 因为前3个题的难度较大, 并且甲错误估计了B和C的难度, 过度纠结B和C, 但是甲看出D是个dp后, 及时和乙进行了交流并想出了思路; 对于简单题M, 丙的思考方向出错导致一直没有思路, 由于部分题丙的题意理解上遇到了困难, 于是纠结在了K这个较难的题上; 所以在读题时间的后期, 甲和乙在看B这个难题, 而丙则在剩下的几个题中看; 当开始敲代码时, 乙在敲完第一个签到题后, 因为第二个签到题没有考虑充分wa了一发; 乙在做完2道题签到后, 发现自己有思路的I题在复杂度上有点极限, 可能会tle, 于是先放了放; 此时我们发现rank中大部分人都完成了M题, 于是乙和找读M题丙去想M题, 甲也并放弃了难题B去做已经有人ac的L题; 好在我们在第一个小时前ac了M题和L题, 而I题三人都没有更好的思路, 所以乙决定搏一搏, 结果也成功ac; 至此用了1小时15分钟ac了5道题, 我们有思路的题就只剩下了D题, 乙写完后发现wa了, 所以我们剩下的时间一直在想怎么做D题, 一直到比赛结束; 结果赛后发现是数据爆unsigned LL, 要用double存才可以...




【GCPC 2023】题目解析

A - Adolescent Architecture 2(Gym - 104466A)

难度 ⭐⭐⭐⭐⭐

【题目描述】

三年前,你帮助小彼得把他的玩具积木堆成一座塔。从那时起,他扩展了他的玩具积木系列,现在具有以下基本形状:圆圈 a– 半径为一个圆a; 正方形 a– 有边长的正方形a; 三角形 a– 边长为等边三角形a。
这里,AA可以是任何正整数。每个块的顶部形状与其底部形状相同,因此块分别是长方体、圆柱体和三棱柱。彼得拥有无限量的各种形状和大小的方块。彼得和他的朋友艾米正在玩一个两人游戏,其中的积木需要堆叠在一起。最初,一些块已经放置在地板上。在每次移动中,当前玩家必须从无限供应中取出一个玩具块,并将其放在现有的一堆块的顶部。在放置块之前,可以绕其垂直轴旋转该块。新区块的轮廓必须严格在旧区块的轮廓内;轮廓不允许接触。第一个无法采取行动的玩家输掉了游戏。
给定初始配置,确定第一个玩家获胜的步数。

【题目模型】

有多个块堆栈, 玩家轮流在这些上面放置积木; 第一个无法移动的玩家输; 每一块都必须严格地放在它下面的一块之内; 有三种具有任意整数大小的块的形状: 圆形、三角形和正方形; 积木个数n为1e3, 边长a为1e9;




B - Balloon Darts(Gym - 104466B)

难度 ⭐⭐⭐⭐

【题目描述】

一个二维平面上有n个气球, 给出所有气球的坐标; 你现在有3个飞镖, 你可以在任意位置朝任意方向射出, 并且距离是无限远; 请问能否用这3个飞镖射爆所有气球;

【题目模型】

给定n个点, 最多用3条线覆盖所有点; n为1e4;

【解题思路】

如果要用3条线遍历所有点, 那么对于任意的4个点, 其中至少有2个点在一条直线上; 我们可以先从所有点中任选2个点形成一条直线, 然后把所有在这条线上的点去掉; 接下来我们要用2条线遍历所有点, 那么对于任意的3个点, 其中至少有2个点在一条直线上; 于是我们可以再从剩下的点中任选2个点形成一条直线, 然后把所有在这条线上的点去掉; 接下来再判断一下剩下所有点是否在同一条直线上即可;
上述操作可以用递归实现, 用飞镖数k表示我们要从多少点里面选2个点, 既然在是任意的(k+1)个点, 至少有2个点在一条直线上, 那么我们就选v[1]~v[k+1]即可; 在这之前我们先判断一下, 如果当v中的点数小于等于2*k时一定可以遍历所有点, 所以这时候直接return true即可, 这也防止了遍历时越界; 因为选的两个点不分先后, 所以遍历时j从i+1开始以防重复; check函数用于检查三点共线, 具体方法就是看两两斜率是否相同, 高中方法不多赘述;
因为k=3时会遍历C(4,2) = 6条边, 因此k=2时会遍历C(3,2) = 3条边; 每枚举一条边时都会遍历所有点以去除共线的点, 所以复杂度就是O(18 * n);

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
struct str {
	int x, y;
};
int n;

bool check(str c, str a, str b) {
	int d1 = (a.x - b.x) * (b.y - c.y);
	int d2 = (b.x - c.x) * (a.y - b.y);
	if (d1 == d2) return true;
	else return false;
}
vector<str> modify(vector<str> v, str a, str b) {
	vector<str> v2;
	for (int i = 0; i < v.size(); i++) {
		str t = v[i];
		if (!check(t, a, b)) v2.push_back(t);
	}
	return v2;
}
bool solve(vector<str>& v, int k) {
	if (v.size() <= 2 * k) return true;
	for (int i = 1; i <= k + 1; i++) {
		for (int j = i + 1; j <= k + 1; j++) {
			auto v2 = modify(v, v[i], v[j]);
			if (solve(v2, k - 1)) return true;
		}
	}
	return false;
}
signed main() {
	cin >> n;
	vector<str> v;
	for (int i = 1; i <= n; i++) {
		int a, b;
		cin >> a >> b;
		v.push_back({ a,b });
	}
	if (solve(v, 3)) cout << "possible";
	else cout << "impossible";
	return 0;
}




C - Cosmic Commute(Gym - 104466C)

难度 ⭐⭐⭐
【题目描述】

很久以前,在一个遥远的星系中,宇宙间通道公司使用轻轨运营着一个复杂的铁路系统。每个行星只有一个火车站,每个轻轨连接银河系的两个不同的行星,在它们之间来回穿梭。就在最近,宇宙间通道公司建立了一个传送系统,目前正处于测试阶段。一些火车站现在被虫洞延伸。所有虫洞都相互连接,可以瞬间从一个虫洞传送到另一个虫洞。为了不使新系统过载,银河系的每个公民每天最多只能传送一次。查理住在加利弗里星球,在桑塔星球工作。这是她上班的第一天,她已经迟到了,因为她愚蠢的闹钟没有响。最重要的是,新的传送系统今天全天出现故障,无法选择目的地虫洞。相反,在进入虫洞后,一个人会被传送到一个虫洞,该虫洞在所有其他虫洞中随机选择。(传送后不可能在同一个火车站); 尽管她运气不好,但查理还是一心想按时上班。由于所有的轻轨都非常慢,她想尽可能少地乘坐轻轨。如果她最多可以使用一次(故障)传送系统,她上班所需的最少轻便列车数量是多少?

【题目模型】

给定一个n个点, m条边权值均为1的无向图, 要求从1点走到n点, 图上有k个点配有传送装置, 但是该传送装置会随机传送到其他有传送装置的点, 并且传送装置最多用一次; 问从1到n的路程最小值是多少, 答案以最简分数表示; n是2e5, m是1e6, k是2e5;

【解题思路】

如果不用传送装置, 最短路程就1~n的最短路; 如果要用就会发生概率问题, 因为有(k-1)个可能的目的地, 所以要把所有可能的路程长度加起来除以(k-1); 因为本题权值均为1所以直接用bfs求距离即可, 用两次bfs求出所有点到1点和n点的距离, 我们设为dst和ded; 当我们在点x使用传送装置时, 假设到达了点y, 那么该次路程长度为dst[x]+ded[y]; 因为点y有(k-1)种可能, 所以总的路程就是dst[x](k-1) + (ded[y1]+ded[y2]+...ded[yk-1]); 但是这样描述是O(kk)的复杂度肯定会超时, 所以我们可以先预处理出所有虫洞点的ded之和sum, 然后每次遍历到一个虫洞点x时让sum-ded[x]再加上dst[x]*(k-1)就是距离的总和, 这样复杂度就优化为O(k);
用两次bfs复杂度为O(n+m), 遍历每个虫洞点是O(k); 所以最终复杂度就是O(n+m)

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 10;
int n, m, k, idx;
int h[N], ne[N], e[N];
int dst[N], ded[N];
vector<int> v;
void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}
void bfs(int u, int d[]) {
	for (int i = 1; i <= n; i++) d[i] = -1;
	queue<int> q;
	q.push(u);
	d[u] = 0;
	while (q.size()) {
		int t = q.front();
		q.pop();
		for (int i = h[t]; i != -1; i = ne[i]) {
			int j = e[i];
			if (d[j] == -1) {
				d[j] = d[t] + 1;
				q.push(j);
			}
		}
	}
}
signed main() {
	cin >> n >> m >> k;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= k; i++) {
		int a;
		cin >> a;
		v.push_back(a);
	}
	for (int i = 1; i <= m; i++) {
		int a, b;
		cin >> a >> b;
		add(a, b), add(b, a);
	}
	bfs(1, dst), bfs(n, ded);
	int minn = ded[1] * (k - 1);
	int sum = 0;
	for (int i = 0; i < v.size(); i++) sum += ded[v[i]];
	for (int i = 0; i < v.size(); i++) {
		int ans = dst[v[i]] * (k - 1) + sum - ded[v[i]];
		minn = min(minn, ans);
	}
	int x = gcd(minn, k-1);
	int fz = minn / x, fm = (k - 1) / x;
	cout << fz << '/' << fm;
	return 0;
}




D - DnD Dice(Gym - 104466D)

难度 ⭐⭐

【题目描述】

在龙与地下城(DnD)和许多其他角色扮演游戏中,许多动作都是由掷骰子决定的,使用不同边数的骰子也很常见。最常见的骰子是基于五个柏拉图固体的骰子,四面体,立方体,八面体,十二面体和二十面体; 在 DnD 术语中,这些骰子通常称为 d4、d6、d8、d12 和 d20。作为地牢大师,您目前正在为您的玩家团队设计战役。在这场战役的最后一战中,玩家需要掷出多个面数不同的骰子的组合,敌人的行动由掷出的骰子上的数字之和决定。出于平衡目的,您希望根据这些总和发生的可能性对这些总和进行排序,以便可以为每个总和分配适当的事件。给定每种类型的骰子数量,并假设每个骰子的边数从1对于边数,找到所有可能的掷骰子总和,并按非递增概率排序输出它们。

【题目模型】

现有5种骰子, 最大点数分别为4,6,8,12,20; 现在给出这五种骰子各自的数量, 掷出后把所有骰子的点数相加得到一个总点数, 按概率从大到小输出所有可能的总点数; 每个骰子最多10个;

【解题思路】

一道非常像dp的找规律, 当然dp也能做, 但是统计的数量数值太大爆unsigned LL, 必须要用double存才能过, 当时以为是状态计算出错了, 没想到是爆unsigned LL... 正解的做法是用正态分布来做; 满足正态分布的条件为: 概率受到若干独立因素共同影响, 其每个因素不能产生支配性作用; 本题的点数由若干的骰子点数组成所以满足该条件;正太分布一定是对称的, 所以可以先判断一下区间长度, 找出概率最大的一个或者两个数, 然后向左右发散即可;
如果要用dp的话要遍历三层, 第一层是骰子的数量, 第二层是当前骰子可以掷出的点数, 第三层是目前可以累加到的总点数, 因为前两层加起来就是遍历所有可能的点数, 可总点数最大为500, 所以最终复杂度就是O(500*500);
如果是正态分布的话只需要从中间值向左右发散, 所以复杂度最大就是O(500);

神秘代码

//正态分布做法
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<double, int> PII;
const int N = 1e3 + 10;
int a, b, c, d, e;
int l, r;
signed main() {
	cin >> a >> b >> c >> d >> e;
	int minn = a + b + c + d + e;
	int maxn = a * 4 + b * 6 + c * 8 + d * 12 + e * 20;
	int len = maxn - minn + 1;
	if (len % 2==0) {
		l = (minn + maxn) / 2;
		r = (minn + maxn) / 2 + 1;
		cout << l << ' ' << r << ' ';
	}
	else {
		l = r = (minn + maxn) / 2;
		cout << l << ' ';
	}
	while (1) {
		if (l<minn && r>maxn)break;
		if (--l >= minn) cout << l << ' ';
		if (++r <= maxn) cout << r << ' ';
	}
	return 0;
}
//dp做法
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<double, int> PII;
const int N = 1e3 + 10;
int a, b, c, d, e;
int s[N], cnt, sum;
double dp[N][N];
PII res[N];
bool cmp(PII a, PII b) {
	return a.first > b.first;
}
signed main() {
	cin >> a >> b >> c >> d >> e;
	sum = a * 4 + b * 6 + c * 8 + d * 12 + e * 20;
	for (int i = 0; i < a; i++) s[++cnt] = 4;
	for (int i = 0; i < b; i++) s[++cnt] = 6;
	for (int i = 0; i < c; i++) s[++cnt] = 8;
	for (int i = 0; i < d; i++) s[++cnt] = 12;
	for (int i = 0; i < e; i++) s[++cnt] = 20;
	dp[0][0] = 1;
	for (int i = 1; i <= cnt; i++) {
		for (int j = 1; j <= s[i]; j++) {
			for (int k = j; k <= sum; k++) {
				if (dp[i - 1][k - j]) {
					dp[i][k] += dp[i - 1][k - j];
				}
			}
		}
	}
	int num = 0;
	for (int i = cnt; i <= sum; i++)  res[++num] = { dp[cnt][i],i };
	sort(res + 1, res + 1 + num, cmp);
	for (int i = 1; i <= num; i++)  cout << res[i].second << ' ';
	return 0;
}




E - Eszett(Gym - 104466E)

难度 ⭐

【题目描述】

对于那些试图学习德语的人来说,字母“ß”,称为Eszett或sharp S,通常是造成巨大混乱的根源。这个字母是德语独有的,它看起来类似于“b”,但发音像“s”。更令人困惑的是,直到几年前,标准德语正字法中还只存在“ß”的小写版本。在需要大写字母“ß”的地方,例如在法律文件和商店标志中,它被(通常仍然是)大写的双字母“SS”所取代。2017年,大写字母“ẞ”正式引入德语,现在可以在这些场景中使用。除了让外国人感到困惑之外,用“SS”替换“ß”的做法也引入了一些歧义,因为具有一个或多个“SS”出现的给定大写单词可能对应于多个不同的小写单词,具体取决于每个“SS”是大写的“ß”还是“ss”。给定一个大写单词,找到它可以派生的所有小写单词。由于字母“ß”不属于 ASCII 范围,请改为大写字母“B”。

【题目模型】

给定一个由大写字母组成的字符串, 将其转化成小写字母输出, 然后在转换后我们可以将"ss"转化为B, 并且保证字符串中最多只有3个s; 输出所有可能的字符串, 无论顺序; 字符串长度最大为20, S最多出现3次;

【解题思路】

签到题, 可以记一下string的replace函数: replace(下标, 长度, 字符串); 作用是用该字符串替换从下标开始的某长度的字符串; 返回值是修改后的字符串; 所以该函数相当于遍历了一遍字符串, 所以复杂度就是O(n);

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5;
signed main() {
	string s;
	string res;
	cin >> s;
	for (int i = 0; i < s.size(); i++) s[i] = s[i] + 32;
	cout << s << endl;
	if (s.find("sss")!=-1) {
		cout << s.replace(s.find("sss"), 3, "Bs") << endl;
		cout << s.replace(s.find("Bs"), 2, "sB") << endl;
	}
	else if (s.find("ss") != -1) {
		cout << s.replace(s.find("ss"), 2, "B") << endl;	
	}
	return 0;
}




F - Freestyle Masonry(Gym - 104466F)

难度 ⭐⭐⭐⭐

【题目描述】

弗雷德的任务很简单,他只需要构建一个w×h墙。为了让这一切变得更加容易,他获得了足够的2×1砖头还有一些1×1砖块来完成墙壁。弗雷德知道这项任务应该不会太难,所以没有考虑太多设计就开始建造这堵墙。只有当他用完的时候1×1砖头,弗雷德注意到这可能是一个坏主意; 也许他应该在开始建造隔离墙之前制定一个计划,但现在已经太晚了。弗雷德只有一堆2×1剩下的砖块想要完成墙壁。剩下的他还能完成吗?请注意,要建造的墙的宽度应恰好为w单位和高度正好h单位。

【题目模型】

给定一个n*m的矩形平面, 其中最底层已经堆好了一部分大小为1x1的正方形, 给出每一列已经堆好的正方形高度, 问能否用大小为1x2的矩形恰好填好剩余的平面; n为2e5, m为1e6

【解题思路】

刚读完题以为是状压dp, 但是数据范围高达1e6, 自然只能想其他办法; 并且我们也能从中得知遍历n*m的矩形也一定会超时; 要用O(n)的复杂度解决问题就只能找规律或者找公式了; 所以可以猜测这是一个贪心问题; 既然要砌好所以墙面, 我们不妨从左侧开始, 因为要找局部最优解, 所以我们要尽可能不能不去干涉其他列, 所有应该优先竖着砌, 如果空不够了再横着砌;
在纸上模拟多种情况后发现, 只有两种情况我们必须横着砌, 一是还有一格就到顶, 二是还有一格就到上一列横着砌的砖; 第一种情况最好处理, 直接让下一列的高度上限减一即可; 而第一种情况我们可以再往前推, 上一列的这个位置之所以会横着砌无非也是那两种情况, 如果是第一种情况说明当前这一列往上就没有空位了直接考虑下一列即可; 如果是第二种情况就说明当前这一列在高度上限的上面还有空着的地方, 并且高度最大为1, 所有这个地方也要砌一个横放的砖; 于是下一列如果可以恰好到达高度上限, 那么他会要么直接到顶, 要么就是空一格后又到达一个新的高度上限, 所以下下列的高度上限就是min(maxn+1, m);
因为对于每一列我们可以直接对2取余就能判断是否恰好到达上限, 如果到达更新一下上限即可; 所以只需要遍历w即可, 复杂度为O(n);

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
int n, m;
int h[N];
signed main() {
	cin >> n >> m;
	bool f = true;
	for (int i = 1; i <= n; i++) {
		cin >> h[i];
		if (h[i] > m) f = false;
	}
	if (!f) {
		cout << "impossible";
		return 0;
	}
	int maxn = m;
	for (int i = 1; i <= n; i++) {
		int d = maxn - h[i];
		if (d < 0) break;
		if (d % 2) maxn--;
		else {
			maxn = min(maxn + 1, m);
		}
	}
	if (maxn == m) cout << "possible";
	else cout << "impossible";
	return 0;
}




G - German Conference for Public Counting(Gym - 104466G)

难度 ⭐

【题目描述】

格蕾塔喜欢数数。她一年中的每一天都在练习。根据季节的不同,她数着落叶、雨滴、雪花,甚至是正在生长的叶子。然而,夏季有一个最重要的活动:德国公共计数会议 (GCPC)。此次活动,格蕾塔与来自全国各地的计数爱好者们相约,为期一周的计数、计数、计数……一起参加魅力竞技公共计数和大混乱公共计数。在本周末,他们都试图赢得公共计票金杯。她最喜欢的是“温和平静的公共计数”,人群默默地计数,试图和谐地同步,在同一时刻达到目标数字。为了增加紧张感,并为温和平静的公众点票做准备,GCPC的组织者计划以无声倒计时开始,台上的人们随时举起带有数字的标牌来显示当前的数字。每个符号上都只有一位小数。数字大于99通过举起几个相邻的标志来显示。每个数字都使用尽可能少的符号来显示;没有左填充为零。这样舞台上的人就显示数字n,n-1,n-2,……,直到他们最终显示0。由于GCPC即将召开,主办方希望尽快完成准备工作。他们至少需要准备多少个标牌才能显示从n到0?

【题目模型】

现在有很多个牌子, 牌子上有一个0~9之间的一个数字; 现在给定一个数n, 问我们至少需要多少个牌子可以把0~n中任意一个数表示出来; n为1e9;

【解题思路】

签到题, 对于n(n>1)位数的数, 一定存在一个n-1位的数是由同一个数组组成; eg: 0~23213中一定存在1111, 2222...10000; 所有0~9中每个数字的牌子都要至少准备n-1个; 而0~23213中还存在11111和22222, 所有再根据第n位的数遍历其他位的数就行; 复杂度最大为O(n)(n是位数, 最大为9);

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5;
signed main() {
    string s;
    cin >> s;
    if (s.size() == 1) cout << s[0] - '0' + 1;
    else {
        int res = 10 * (s.size() - 1);
        res = res + (s[0] - '0') - 1;
        if (s[1] - '0' > s[0] - '0') res++;
        else if (s[1] - '0' == s[0] - '0') {
            bool f = true;
            for (int i = 1; i < s.size(); i++) {
                if (s[i] < s[0]) {
                    f = false;
                    break;
                }
            }
            if (f) res++;
        }
        cout << res;
    }
    return 0;
}




H - Highway Combinatorics(Gym - 104466H)

难度 ⭐⭐⭐⭐⭐

【题目描述】

你是伯兰的新任交通部长。最近,您允许在200米长。从那时起,由于一些天才司机的想法,该路段一直被停放的汽车堵塞,他们的想法是将车停在两条车道上...但是,这不是您关心的问题。您更感兴趣的是将自己的一些汽车停在空无一人的路段上。更具体地说,您希望以这样一种方式停放一些汽车,即用汽车填充剩余空白空间的不同方法的数量与您的幸运数字一致 把n对1e9+7; 每辆车的尺寸为1×2米,两条车道中的每一条都是1米宽和200米长。您拥有的不仅仅是200您可以停在路段的汽车。

【题目模型】

给定一个2*200的长方形区间, 用n个汽车填充, 问方法有多少种; n最多为1e9+7;




I - Investigating Frog Behaviour on Lily Pad Patterns(Gym - 104466I)

难度 ⭐

【题目描述】

最近,生物学家艾娜在池塘的睡莲叶上发现了一种新的青蛙物种。她观察了这些青蛙一段时间,发现它们非常注重自己的个人空间,因为它们避免与其他青蛙共用睡莲叶。而且它们看起来也很懒,不经常动,动的话也总是跳到最近的空睡莲叶上。为了证实她对青蛙运动模式的假设,伊娜在实验室的水池中放置了大量睡莲,并排成一条直线。由于青蛙被光吸引,她可以通过在该线的一端放置一盏强光来进一步简化测试设置。这样,青蛙总是会向一个方向(向着光)跳跃。当然,伊娜现在可以在睡莲叶上放一些青蛙,然后整天坐在那里看着青蛙跳来跳去。但由于青蛙很少活动,因此需要很长时间才能收集足够的数据。因此,她给每只青蛙安装了一个微型装置,可以记录该青蛙的所有跳跃。这样,她就可以将青蛙放在睡莲叶上,让它们单独放置几个小时,然后再回来收集数据。不幸的是,这些设备必须非常小,以至于没有空间容纳位置跟踪系统;相反,设备只能记录跳跃的时间。但如果青蛙的运动模式像伊娜想象的那样受到限制,那么青蛙的个体运动肯定只能根据初始位置和记录的跳跃时间戳来重建吗?

【题目模型】

在一排荷叶上有n只青蛙, 给出每只青蛙的下标x; 给出m次操作, 让最初的第i只青蛙往前跳到距离它最近的空着的荷叶上, 输出每次操作后该青蛙的落地荷叶坐标; n为2e5, x为1e6, m为2e5

【解题思路】

一道比较的简单的模拟题, 用变量maxn表示当前最远的荷叶坐标, 用set维护空着的荷叶, 每次操作用二分找出距离它最近的空着的荷叶, 然后更新set序列; 如果找不到就跳到maxn+1上, 然后更新maxn即可; 比赛时还担心进行1e6次二分查找和set的插入删除会超时, 结果说明本题还是比较仁慈的; set的插入和二分都是O(logn)的复杂度, 所以总的复杂度就是O(nlogn);

神秘代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
set<int> st;
map<int, int> mp;
int f[N];
signed main() {
	cin >> n;
	int maxn = 0;
	for (int i = 1; i <= n; i++) {
		cin >> f[i];
		mp[f[i]]++;
		maxn = max(maxn, f[i]);
	}
	for (int i = 1; i <= maxn; i++) {
		if (!mp[i]) st.insert(i);
	}
	cin >> m;
	for (int i = 1; i <= m; i++) {
		int x;
		cin >> x;
		auto t = st.upper_bound(f[x]);
		if (t == st.end()) {
			st.insert(f[x]);
			maxn++;
			f[x] = maxn;
			cout << f[x] << endl;
		}
		else {
			int a = *t;
			st.erase(t);
			st.insert(f[x]);
			f[x] = a;
			cout << f[x] << endl;
		}
	}
    return 0;
}




J - Japanese Lottery(Gym - 104466J)

难度 ⭐⭐⭐⭐⭐

【题目描述】

阿弥陀签是一种在日本流行的彩票,可以用来分配w奖品给w人们。游戏由w垂直线,称为腿,以及一些连接相邻腿的水平条。腿的顶部是起始位置w人,奖品就在腿的底部。为确定该奖项的第i个人,必须向下移动第i个腿,从顶部开始,每当遇到横杆时就换腿。您可以在下面的可视化中看到这样的游戏以及如何追踪路径。你想以这样的方式操纵彩票让第i个人得到第i个奖,对于每个i,通过删除一些水平条。由于您不想被抓住,因此您希望删除尽可能少的横杠。
对于这个问题,初始游戏配置是没有横杠的。然后,水平条被一根一根地添加或再次移除。每次更改后,您想知道需要删除的水平条的最小数量,以便第i个奖被分配给第i个人。请注意,通过删除所有水平条始终可以实现这一点。

【题目模型】

最初给定n条竖线, 第i条线连接上方i和下方i两个相同数字的点, 之后会进行q次操作, 可以在h高度上在x和y两条竖线之间加或者删一条横杠, 因为上方的点往下走时遇到横杠就要拐弯, 所以会偏离该竖线, 问此时我们最少删掉多少横杠可以用所有的上方的第i个点走到下方的第i个点;




K - Kaldorian Knights(Gym - 104466K)

难度 ⭐⭐⭐

【题目描述】

卡多利亚国王传统上会邀请王国的骑士参加大型马上比武锦标赛来庆祝他的生日,每个贵族家族都会派出他们最好的骑士参加,以赢得名声和荣耀。比赛结束时,国王不仅会选出获胜者,还会对所有选手进行排名, 骑士从最差到最好。
所属家族i的骑士数量表示为ki​。每个骑士最多服务一个家族。可能有不为任何家族服务的骑士。这些家族按照其在王国中的影响力排序(第一个是最有影响力的)。如果k1​最强大家族的骑士取得最后胜利k1​在锦标赛中,众议院将煽动对国王和王冠的反抗。第二大家族的成员并没有那么强大。即使这一切k2​骑士最终排名垫底,虽然算是强烈挑衅,但家族却无法发动叛乱。然而,如果最后k1​+k2​两个最有影响力的家族的所有骑士联合起来占据席位,然后两个家族就会联合起来,开始与国王作战。更一般地说,如果骑士ℓ最强大的家族占据最后k1+k2+⋯+kℓ​在锦标赛中,他们将团结起来煽动叛乱。当然,必须不惜一切代价避免叛乱。知道国王经常自发地选择排名,而不需要太多考虑,因此王室首席数学家的任务是分析多少排名不会导致叛乱。

【题目模型】

现在有n个骑士和m个家族, 按照家族实力从大到小输入每个家族的骑士数量ki, 可能存在骑士不属于任何家族; 国王会对骑士进行排名; 如果最强家族的k1名骑士被排在最后k1名, 那么该家族就会叛乱; 第二强的家族实力较弱, 即使他的k2名骑士被排在最后k2位也不会叛乱, 但是, 如果最强和第二强两个家族的(k1+k2)名骑士被排在最后(k1+k2)位, 那个这两家会联合叛乱, 相应的如果(k1+k2...ki)名骑士被排在最后(k1+k2...ki)位, 那么前i家家族就会联合叛乱; 问国王有多少种排序方式可以不引发叛乱, 结果对1e9+7取模; n为1e6, m为6e3

【解题思路】

一道数学排列问题, 难点在于找到不同排列之间的重合部分; 我们设di表示前i个家族的骑士排在末尾的情况, 并且di与前面的d(1~i-1)没有重复情况;
当我们只考虑第一家族时: d1就是A(k1);
考虑前两个家族时: 设f=k1+k2, d2不能单纯的用A(f)表示, 因为A(f)和d1一定存在重合的情况, 即A(f)中k1全部排在最后的情况, 这种情况下k2的排序一共有A(f-k1)种, 而k1则是d1种, 所以d2=A(f)-A(f-k1)d1;
考虑前三个家族时: 设f=k1+k2+k3, A(f)既和d1有重合, 也与d2有重合; 与d1的重合就是A(f)中k1全部排在最后的情况, 所以A(f)要减去A(f-k1)
d1; 与d2的重合就是A(f)中k1和k2全部排在最后. 并且最后全是k1的情况, 所以A(f)要减去A(f-k1-k2)*d2;
注意我们最后都是乘的是d2而不是A(k1+k2), 因为d2是因为去重后的情况, 如果是A(k1+k2)就减多了; 之后也都同理, 用一个循环遍历i之前的所有情况即可, 并且为了便于计算, 我们可以把k表示为前缀和;
最后的结果就是用A(n)减去所有的A(n - ki) * di; 注意问题有个坑点在于取模上, 因为大部分运算都是减法, 为了防止减的数过大, 我们可以在乘完后先取模在做减法, 然后再加模取模即可;
_____332221111
_______2221111
__________1111
因为每次考虑到第i个家族时都要遍历1~i个家族的情况以去重, 所以复杂度就是O(n2);

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<double, int> PII;
const int N = 1e6 + 10, mod = 1e9 + 7;
int n, m;
int k[N], A[N], d[N];
signed main() {
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		cin >> k[i];
		k[i] += k[i - 1];
	}
	A[0] = 1;
	for (int i = 1; i <= n; i++) {
		A[i] = A[i - 1] * i % mod;
	}
	for (int i = 1; i <= m; i++) {
		d[i] = A[k[i]];
		for (int j = 1; j < i; j++) {
			d[i] = ((d[i] - A[k[i] - k[j]] * d[j] % mod) + mod) % mod;

		}
	}
	int ans = A[n];
	for (int i = 1; i <= m; i++) {
		ans = ((ans - A[n - k[i]] * d[i] % mod) + mod) % mod;
	}
	cout << ans;
	return 0;
}




L - Loop Invariant(Gym - 104466L)

难度 ⭐⭐

【题目描述】

历史学家卢娜(Luna)在探索一座古老修道院的档案时偶然发现了一卷神秘的羊皮纸。上面只有两种类型的符号:“(”和“)”。很快,她注意到符号序列满足一个有趣的属性:它可以通过在某个位置重复插入“()”到初始空序列中来构造。历史学家称这种序列是平衡的。修道院的首席图书管理员最近告诉卢娜,该地区一些更精英的僧侣习惯于在圆形羊皮纸上写字。在他们的脑海中,任何无法立即分辨出这种卷轴上的文字从哪里开始的人也不配知道它的内容。因此,露娜迅速检查了羊皮纸条的边缘。果然,羊皮纸条左右端的边缘完美地贴合在一起,表明羊皮纸曾经是圆形的。她一边将左右边缘放在一起,一边看着现在的圆形羊皮纸,一边想知道从撕裂开始的平衡序列是否是唯一可能由撕裂羊皮纸引起的序列。毕竟,当您甚至不知道文本从哪里开始时,尝试解密文本是没有意义的。

【题目模型】

给定一个由括号组成的序列, 将该序列首尾相连形成一个环, 问我们能否找到一个切割点, 使得在这个位置切开后形成的序列和原序列不同; 切割点不能处于任何一个括号内部, 也就是说要在两个独立的括号之间切; 序列长度n为1e6;

【解题思路】

本题模拟并不算难, 但是如果你遍历所有可行的切割点来找不同的序列, 那么恭喜你tle了; 本题其实只需要找到第一个可行的切割点就可以了, 如果切开后发现与原序列相同, 你再往后找切割点就相当于重复前面的过程, 你会陷入一个循环; 所以只要第一个切割点生产的序列相同就输出no, 否则输出当前序列即可; 处理环状时一般都是把序列延长一倍来模拟; 所以遍历一遍序列即可, 复杂度为O(n)

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, m;
string s, ds;
vector<int> v;
signed main() {
	cin >> s;
	ds = s + s;
	int len = s.size();
	for (int i = 0; i < len; i++) {
		if (ds[i] == '(') v.push_back(i);
		else if (ds[i] == ')') v.pop_back();
		if (v.size() == 0) {
			string str = ds.substr(i + 1, len);
			if (str == s) cout << "no";
			else cout << str;
			break;
		}
	}
	return 0;
}




M - Mischievous Math(Gym - 104466M)

难度 ⭐

【题目描述】

麦克斯喜欢玩数字游戏,无论是寻找导致给定结果的组合,还是发现某些给定整数的所有可能结果。问题是麦克斯只有10岁,数学知识有限,这限制了这些游戏的可能性。幸运的是,在今天的数学课上,麦克斯学习了括号的概念及其对计算的影响。他意识到在数字游戏中加入括号可以让数字游戏变得更有趣。放学回家后,他让妹妹尼娜和他一起玩一种他最喜欢的数字游戏,使用括号。
在这个新游戏中,麦克斯首先告诉她一个数字d。尼娜然后告诉他三个数字a,b,c。现在,麦克斯需要使用这三个数字中的每一个来找到一个使用加、减、乘、除的算术表达式(a, b和 c 最多一次),使得结果等于d。数字a,b,c, d所有内容都必须不同,并且麦克斯也可以使用括号。尼娜很快就对这个游戏感到恼火。她宁愿和朋友们一起度过一个下午,也不愿和弟弟一起玩游戏。所以,她想给他一个尽可能长时间占据他的任务。帮助她找到三个数字a,b和c以至于麦克斯不可能想出解决办法。

【题目模型】

给定一个数d; 需要我们找出三个数a,b,c; a,b,c三个数之间可以用加减乘除已经括号进行运算, 要求无论怎么运算都不能得到d; 其中a,b,c,d互不相等且a,b,c在运算时都最多使用一次; 数字大小最大为100

【解题思路】

找特殊值就行, 最简单的逻辑就是对于一个数d, 我们要么让a,b,c运算的最大值小于d, 要么就让运算的最小值大于d; 因此我们最容易想到1,2,3, 其运算的最大值就是9, 因此如果d>9就输出1,2,3;
d<=9时自己找一组相互差值大于9的且比较怪的三个数就大概率可以过, 据题解说一共有16170种组合;
所以复杂度为O(1);

神秘代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5;
int n, m;
signed main() {
	cin >> n;
	if (n > 9) cout << "1 2 3 ";
	else cout << "49 60 82";
	return 0;
}
posted @ 2023-08-04 21:26  mostimali  阅读(335)  评论(0编辑  收藏  举报