赛事总结
比赛形式
- 首次线上双机位校赛
验题
- 每题至少安排两个人验题
- 数据得到保障
L1-3
由原先需要unsigned long long
或者py
才能过,改为只需要LL
能过L2-1
中担心有考生忘记闰年如何判断,增加了闰年以及每个月的月数L2-4
增强了数据,直接转十进制,再转回来过不了L3-2
卡掉了暴搜能拿接近满分L3-3
的难度得到平衡
题型
为了让题型更加符合天梯风格,我砍掉了6
道其他出题人想出的题,最终才展现出这份卷子
题目难度
- 凭心而论难度把控确实很成功,每题都有人
AC
,但是就是没有人AK
L1
L1 - 1 刀使女巫
出题人:贝
题目大意
《刀使女巫》
的主角的头发颜色是什么
解题思路
正确答案为E
以下三种解题方法
- 知道天梯赛总决赛于
4
月23
号比赛 - 知道
《刀使女巫》
的主角是棕色头发 - 枚举大法
代码
#include<stdio.h>
int main(){
printf("E");
}
L1 - 2 复读机
出题人:贝
思路
- 妥妥送分题,没什么好说的
代码实现
#include <bits/stdc++.h>
using namespace std;
#define el '\n'
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
int main()
{
cin.tie(0);
cout.tie(0);
rep(i, 1, 5)
cout << "beibei shi Jimei University zui cai de ren" << el;
}
L1 - 3 抛物线
出题人:赖
题目大意
带入公式求结果
测试点详情 :1-2点送分,3点long long 4点unsigned long long
解题思路
直接带入公式
代码实现
#include <bits/stdc++.h>
using namespace std;
unsigned long long a, b, c, x;
int main() {
cin >> a >> b >> c >> x;
unsigned long long ans = a * x * x + b * x + c;
cout << ans;
}
L1 - 4 死脑筋
出题人:杰
算法思路
兑换后的瓶子,也可以重复兑换,循环结构模拟兑换过程即可,直至不能再兑换
代码实现
#include<bits/stdc++.h>
using namespace std;
int n, k, m;
int main()
{
cin >> n >> k;
int ans = 0;
do {
ans += n;
m += n; //得到n个瓶盖
n = m / k;
m %= k;
}while(n);
cout << ans;
}
L1 - 5 最长公共子串
出题人:弛
题目大意
- T组样例,每组样例求两个字符串的最长公共子串
出题报告
- 测试点就是随机字符串
- 第一个测试点长度字符串的长度控制在21以内
- 第二个测试点是长字符串,长度小于100
解题思路
- 可以看出字符串长度小于100,所以可以直接暴力匹配求解,枚举两个字符串的起始位置,找出最长的子串。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
int t;
scanf("%d",&t);
while(t--){
char a[110],b[110];
scanf("%s%s",a,b);
int ans=0;
char tt[110];
int alen=strlen(a),blen=strlen(b);
for(int i=0;i<alen;i++){
for(int j=0;j<blen;j++){
if(a[i]!=b[j])continue;
char temp[110];
temp[0]=a[i];
int k=1;
while(j+k<blen&&i+k<alen&&a[i+k]==b[j+k]){
temp[k]=a[i+k];
k++;
}
temp[k] = '\0';
if(k>ans){
ans=k;
strcpy(tt,temp);
}
else if(k==ans){
if(strcmp(tt,temp)>0)strcpy(tt,temp);
}
}
}
if(ans==0)
printf("NO\n");
else{
printf("YES\n");
printf("%d\n",ans);
printf("%s\n",tt);
}
}
}
L1 - 6 有色图
出题人:贝
题目大意
在二维数组出现不同的数字的数量是否超过 9 9 9个
出题报告
- 测试点1:小数据 n , m n,m n,m
- 测试点2: n = a i = b i = 100 n=a_i=b_i=100 n=ai=bi=100, x i j x_{ij} xij的范围在 i n t int int之内
- 测试点3: n = a i = b i = 100 n=a_i=b_i=100 n=ai=bi=100, x i j x_{ij} xij的范围在 l o n g l o n g long\ long long long之内
- 测试点3:
n
=
a
i
=
b
i
=
100
n=a_i=b_i=100
n=ai=bi=100,
x
i
j
x_{ij}
xij的范围超过
l
o
n
g
l
o
n
g
long\ long
long long,需要用
map<string, int
来统计
解题思路
使用map<string, int
来统计出现的数的数量即可,最后判断map.size() > 9
是否成立
代码
#include <bits/stdc++.h>
using namespace std;
int T, a, b;
int main()
{
cin >> T;
while (T--)
{
map<string, int> mp;
cin >> a >> b;
string k;
for (int i = 1; i <= a; ++ i)
{
for (int j = 1; j <= b; ++ j)
{
cin >> k;
++ mp[k];
}
}
if(mp.size() > 9)
cout << "YES";
else
cout << "NO" ;
if(T)
cout << '\n';
}
}
L1 - 7 『谁在说谎』
出题人:贝
出题报告
-
原本我还想卡掉double的精度,直接比较总和long long,精度肯定比直接比较平均值来的高,后面思索了一下,还是没刻意去卡
-
这题我设置了4个测试点
-
不会真有人用“lbn”、“LBN”来表示 a 0 a_0 a0对应的字符串?
-
测试点1,基本正确,4分
-
测试点2,最大数据,全部正确,得开long long ,10分
-
测试点3,基本正确,但是会被“lbn”,“LBN”卡掉,5分
-
测试点4,测试nobody,1分
-
因为 1 0 5 × 1 0 5 = 1 0 10 10^5 \times 10^5 =10^{10} 105×105=1010,所以需要开LL
算法思路
- 用map很简单,两个map记录值就行了,map自带排序,记得开LL,秒过
- 自己手写哈希的话也还好,一个字符串最多长度就5位,把他看成26进制即可
代码实现
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 110;
int T, n, m;
map<string , LL> last, now;
string name[N];
string b = "1", s;//因为S是由字母组成的,故用数字字符串来代表贝贝可以避免哈希冲突
//考察哈希中的状态设计
LL x;
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i ++ )
{
cin >> x;
last[b] += x;
}
for (int i = 1; i <= n; i ++ )
{
cin >> s;
name[i] = s;
for (int i = 1; i <= m; i ++ )
{
cin >> x;
last[s] += x;
}
}
cin >> now[b];
int cnt = 0;
for (int i = 1; i <= n; i ++ )
cin >> now[name[i]];
for (auto it : last)
{
if(it.second >= last[b] && now[it.first] < now[b])
{//历史平均值比贝贝高,且报出成绩比贝贝低
cnt ++ ;
cout << it.first << endl;
}
}
if (cnt == 0)
cout << "Yeah! Nobody!" <<endl;
}
L1 - 8 饱了吗公司
出题人:弛
题目大意
- 给出n个点,m条边,询问有几个连通块,并查集
出题报告
- 第一个测试点有环,链,完全图几种情况
- 第二个测试点,10000大小的随机数据
- 第三个测试点,1000000大小的随机数据
- 第四个测试点,100大小的随机数据
- 测试点中有出现边大于点的情况,即出现重复边
解题思路
- 可以用dfs,搜索有几个连通块,也可以用并查集处理,dfs思路直接找到没有搜索过的点搜索即可
- 并查集思路,处理每条边,修改对应点的父节点,最后数有几个节点的父节点是自身即可
代码实现
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
int fa[N];
int find(int x){
if(fa[x]==x)return x;
else return fa[x]=find(fa[x]);
}
int main(){
int T;
cin>>T;
while(T--){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
fa[find(v)]=find(u);
}
int num=0;
for(int i=1;i<=n;i++){
if(fa[i]==i)num++;
}
cout<<n-num<<endl;
}
}
L2
L2 - 1 咸鱼の带四带学生
出题人:勋
题目大意
已知1900年1月1日是星期一,给定一个合法日期询问是星期几
解题
很简单的一道日期模拟,只需要计算两个日期内差了多少天,%7即可。
代码
#include<bits/stdc++.h>
using namespace std;
string s[8]={"1","星期一","星期二","星期三","星期四","星期五","星期六","星期日"};
int main() {
int y,m,d;
scanf("%d-%d-%d",&y,&m,&d);
int sum=0;
for(int i=1900; i<y; i++) {
if((i%4==0&&i%100!=0)||(i%400==0))
sum=sum+366;
else
sum=sum+365;
}
int r[12]={31,29,31,30,31,30,31,31,30,31,30,31};
int br[12]={31,28,31,30,31,30,31,31,30,31,30,31};
for(int i=0;i<m-1;i++){
if((y%4==0&&y%100!=0)||(y%400==0)){
sum+=r[i];
}else{
sum+=br[i];
}
}
sum+=d;
//printf("sum=%d\n",sum);
int t;
t=sum%7;
if(t==0){
cout<<s[7];
}else{
cout<<s[t];
}
return 0;
}
L2 - 2 寻找宜居带
出题人:杰
题目大意
- 给定一棵树,求出高度和深度相同的所有点。
出题报告
- 第一个测试点不存在宜居带
- 其他测试点的树随机生成,大小均为 1 e 5 1e5 1e5
- 测试点就是随机字符串
解题思路
- 维护深度:儿子的深度是父亲的深度 + 1 +1 +1 即可。 d e p [ u ] = d e p [ f a ] + 1 dep[u]=dep[fa]+1 dep[u]=dep[fa]+1
-
维护高度:父亲的高度是所有儿子中最高高度 + 1 +1 +1 。 h i g h [ u ] = m a x ( h i g h [ v ] ) + 1 high[u]=max(high[v])+1 high[u]=max(high[v])+1
-
最后判等即可。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
vector<int>G[N];
int dep[N],high[N],n;
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
high[u]=1;
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(v==fa)continue;
dfs(v,u);
high[u]=max(high[u],high[v]+1);
}
}
int main(){
int u,v;
cin>>n;
for(int i=1;i<n;i++){
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,0);
int ans=0;
for(int i=1;i<=n;i++){
if(dep[i]==high[i]){
ans++;
}
}
cout<<ans<<'\n';
for(int i=1;i<=n;i++){
if(dep[i]==high[i]){
cout<<i<<" ";
}
}
}
L2 - 3 json串转JAVA代码
出题人:赖
题目大意
给你一个json字符串要你转化成java代码
测试点详情 :1-3点送分,4点出现嵌套List 5-6点嵌套list+最后一个是Object类型和object嵌套
解题思路
一行输入所以用getline输入因为不能保证其中有没有空格,所以遇到空格可以直接略过。
观察json显然可以根据":“和”,"轻松分开key和value(只要不在{}和[]内因此要判断现在循环是否在{}和[]内)
分开KV之后分别保存在两个数组内,接下来只要写一个函数分析Value是什么类型的就可以了,显然每一种类型都有自己与其他不一样的样式:
object型:value内第一个字符是'{'
list型:value内第一个字符是'['
string型:value内第一个字符是'"'
bool型:value内第一个字符是字母
Float型:value内包含'.'
如果上面都不是那么一定是Integer型了
是不是很简单呢?
接下来只要简单用代码随便搞搞就可以了。
代码实现
#include <bits/stdc++.h>
using namespace std;
string str, ls = "";
vector<string> lef, rig;
int outTag, outTag2, iss, inList;
string checkType(string key, string str) {
if (str[0] == '{') {
if (iss) {
return "Object";
}
if ('a' <= key[0] && key[0] <= 'z') {
key[0] = key[0] + 'A' - 'a';
}
return key;
}
if (str[0] == '[') {
iss = true;
return "List<" + checkType(key, str.substr(1, str.length() - 2)) + ">";
}
if (str[0] == '\"') return "String";
if (str == "true" || str == "false") return "Boolean";
for (int i = 0; i < str.length(); i++)
if (str[i] == '.') return "Float";
return "Integer";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
getline(cin, str);
for (int i = 1; i < str.length() - 1; i++) {
if (str[i] == ' ') continue;
if (str[i] == '{') outTag2 ++;
if (str[i] == '}') outTag2 --;
if (str[i] == '[') outTag ++;
if (str[i] == ']') outTag --;
if (str[i] == ':' && !outTag && !outTag2) {
ls.erase(remove(ls.begin(), ls.end(), '\"'), ls.end());
lef.push_back(ls);
ls = "";
continue;
}
if (str[i] == ',' && !outTag && !outTag2) {
rig.push_back(ls);
ls = "";
continue;
}
ls += str[i];
}
rig.push_back(ls);
for (int i = 0; i < rig.size(); i++) {
iss = false;
inList = false;
string ans = checkType(lef[i], rig[i]);
cout << "public " << ans << " " << lef[i] << (iss ? "s" : "") << ";\n";
}
}
L2 - 4
贝神的数字
出题人:贝idea,弛出题
题目大意
- 进制转换
出题报告
对于该题,一个关键点是,22
位的62
进制是会超过C++
语言中所有整数类型的最大表示范围的,若采用
- 第一到六个测试点用到 int
- 前六个测试点中有x大于y,x小于y,x等于y,s等于0集中情况,共18分
- 第七个测试点需要用到 long long,2分
- 第八个测试点需要用到int128,2分
- 第九个测试点,3分
- 推荐的做法是,直接从
x
进制转换为y
进制,不借助10
进制的做法可以过 - 不推荐的做法是,使用高精度
- 推荐的做法是,直接从
解题思路
- 最基本的思路是把给出的x进制数先转换成10进制,然后再转换成y进制,转换的过程可以使用短除法,把余数倒叙输出即可,但是当数据过大的时候,无法转换成10进制数。
- 标称使用高精度的做法,把每一位数字当成一个独立的数存储,例如1234,正常存储是一个整数类型赋值为1234,高精则是每一个用一个整数类型,即分成1,2,3,4四个变量,然后模拟期间的进位,借位等。
- 先把输入的数字每位分开存储,还是使用短除法进行转换,除法的过程通过一位一位的处理,模拟除法的过程,而且标程没有转换成10进制,这时需要注意余数部分移动下一位的时候不能乘10,需要乘x。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1500;
int x, y;
char s[MAXN], ans[MAXN];
int t[MAXN], res[MAXN];
int getnum(char c)//将字符转化为数字
{
if(c >= '0' && c <= '9') return c - '0';
if(c >= 'A' && c <= 'Z') return c - 'A' + 10;
else return c - 'a' + 36;
}
char getch(int num)//将数字转化为字符
{
if(num <= 9) return num + '0';
if(num <= 35) return num + 'A' - 10;
else return num + 'a' - 36;
}
void work()
{
int len = (int)strlen(s+1);
for(int i = 1; i <= len; i++) t[i] = getnum(s[i]);
for(int j = 1, k = 0; j <= len; )//j为当前最高位,模拟短除法
{
for(int i = j; i < len; i++)//等价于高精除法
{
t[i+1] += t[i] % y * x;
t[i] /= y;
}
res[++k] = t[len] % y;//得到此次除y的余数
t[len] /= y;
while(j <= len && !t[j]) j++;
for(int i = 1; i <= k; i++) ans[i] = getch(res[k-i+1]);
//倒序输出
}
}
int main()
{
scanf("%d%d%s", &x, &y, s+1);
work();
printf("%s\n", ans+1);
return 0;
}
L3
L3 - 1 最小字典序子序列
出题人:举
题意
一个字符串,找出长度为k的子序列,要字典序最小。
解题思路
既然要使子序列的字典序最小,那么越小的字符应该尽可能的在序列开头。所以应该把最小的字符放在序列中。
分别存在a-z每个字符的下标。假设当前为原字符串s
中下标为now=0
,子序列长度为kk
,从字符a开始找,找到这么一个位置 pos
,使得pos>=now && s.size()-pos>=k-kk
。 s.size()-pos>=k-kk
这个式子是要判断从pos
往后是否还有足够的字符可以构成长度为k的序列。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxm = 2e6 + 5;
vector<int> g[26];
int cur[26];
string s;
int n, k;
void slove() {
for (int i = 0; i < 26; i++) {
g[i].clear();
cur[i] = 0;
}
cin >> s >> k;
n = s.size();
for (int i = 0; i < n; i++) {
g[s[i] - 'a'].push_back(i);
}
string ans;
int now = 0;
int flag = 0;
for (int kk = 0; kk < k; kk++) {
for (int i = 0; i < 26; i++) {
// 使用二分查找进行优化
int idx = (lower_bound(g[i].begin(), g[i].end(), now) - g[i].begin());
if (idx < g[i].size() && (s.size() - g[i][idx]) < (k - kk))continue;
if (idx < g[i].size()) {
ans += ('a' + i);
now = g[i][idx]+1;
break;
}
}
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(false);
slove();
return 0;
}
L3 - 2 逃亡之路
出题人:勋
题目大意
有n个地点,m条路径,同时每条航线有概率出现LJL,输出从起点s到终点t的最短路径,该路径要保证遇到LJL概率最小。
测试数据
1. 完全图
2. 链
3. $n=1000,m=10000$随机数据
4. $n=10000,m=100000$随机数据
5. $n=100000,m=200000$随机数据
6. 无边
7. 多个相同地点数,需要概率最大
解题
很明显解法是最短路径,所谓路过地点数就是将每个路径看成长度为1的最短路。
这里涉及一个最短路径的变形题:多权值最短路径。也就是dijk算更新最短路的时候如果需要相等的情况需要根据第二关键字判断:
要求的路径首先是最短路,其次才是遇到LJL概率最小的路径。求路径上遇到LJL的概率可以用反面事件,1-LJL分身消失的概率,后者是每段路概率乘积。可以维护从起点到各点最短路径上都没遇到LJL的概率,这样就成了一个多权值最短路问题,与普通最短路差别就在于dis[now]+w == dis[to]时,如果此时从当前点到达下一个点会使得第二个权值更优那就更改路线,否则不必改动。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <queue>
#define pii pair<int, int>
using namespace std;
//首先需要路径最短,其次需要遇到LJL概率最低
int n, m, s, t, dis[100005], head[100005], cnt, pre[100005], st[100005];
bool vis[100005];
double p[100005];
struct edge
{
int to, w, next;
}e[500005];
void add(int u, int v, int w)
{
e[++cnt].to = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt;
}
void dijkstra()
{
dis[s] = 1;
p[s] = 1.0;
priority_queue<pii, vector<pii>, greater<pii> > a;
a.push(make_pair(dis[s], s));
while(a.size())
{
int now = a.top().second;
a.pop();
if(vis[now])
continue;
vis[now] = true;
for(int i = head[now]; i; i = e[i].next)
{
int to = e[i].to, w = e[i].w;
if(dis[now]+1 < dis[to])
{
dis[to] = dis[now]+1;
pre[to] = now;
p[to] = p[now]*(100.0-w)/100.0;
a.push(make_pair(dis[to], to));
}
else if(dis[now]+1 == dis[to])
{
if(p[now]*(100.0-w)/100.0 > p[to])
{
p[to] = p[now]*(100.0-w)/100.0;
pre[to] = now;
}
}
}
}
}
signed main()
{
cin >> n >> m >> s >> t;
for(int i = 1; i <= m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add(u, v, w), add(v, u, w);
//cout<<i<<endl;
}
memset(dis, 0x3f, sizeof dis);
dijkstra();
if(dis[t]!=1061109567)printf("%d %.10f\n", dis[t], p[t]);
else cout<<"-1";
return 0;
}
L3 - 3 数线段
出题人:杰
题目大意
- 在一个 1 − n 1-n 1−n 的数轴上给出 m m m 条线段, k k k 次查询,每次查询一个区间内有多少个完整线段。
出题报告
- 前两个测试点 n , m , k = 1 e 3 n,m,k=1e3 n,m,k=1e3 ,允许暴力 c h e c k check check 所有线段通过。
- 后四个测试点 n , m , k = 2 e 5 n,m,k=2e5 n,m,k=2e5,线段与区间均随机生成。
解题思路
- 考虑对询问 [ l , r ] [l,r] [l,r] 的贡献:只有线段左端点 > = l >=l >=l 的线段能对该查询产生贡献,贡献值为:线段右端点在 < = r <=r <=r 的贡献。
- 将所有询问离线,设 q [ l ] [ ] q[l][] q[l][] 为左端点在 l l l 的所有询问。
- 将所有线段离线,设 a [ l ] [ ] a[l][] a[l][] 为左端点在 l l l 的所有线段。
- 考虑从右往左,以逐渐开启线段的方式处理询问
- 当处理到左端点为 l l l 的询问时,将所有左端点在 l l l 的线段的右端点 r r r 位置,单点修改 c [ r ] + + c[r]++ c[r]++
- 那么该询问的答案就为: ∑ i = l r c i \sum_{i=l}^r c_i ∑i=lrci 。
- 单点修改,区间查询,这个操作可以用树状数组完成,单次时间复杂度 O ( l o g n ) O(logn) O(logn)
- 总时间复杂度: O ( ( m + k ) l o g n ) O((m+k)logn) O((m+k)logn)
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
vector<int>a[N];
vector<pair<int,int>>q[N];
int ans[N],c[N],n,m,k;
void update(int x,int k) {
for(int i=x; i<=n; i+=i&(-i))c[i]+=k;
}
int query(int l,int r) {
int sum=0;
for(int i=r; i>0; i-=i&(-i))sum+=c[i];
for(int i=l-1; i>0; i-=i&(-i))sum-=c[i];
return sum;
}
int main() {
int l,r;
cin>>n>>m>>k;
for(int i=1; i<=m; i++) {
cin>>l>>r;
a[l].push_back(r);
}
for(int i=1; i<=k; i++) {
cin>>l>>r;
q[l].push_back(make_pair(i,r));
}
for(int l=n; l>=1; l--) {
for(int i=0; i<a[l].size(); i++) {
int r=a[l][i];
update(r,1);
}
for(int i=0; i<q[l].size(); i++) {
int id=q[l][i].first,r=q[l][i].second;
ans[id]=query(l,r);
}
}
for(int i=1; i<=k; i++)cout<<ans[i]<<" ";
return 0;
}