CPPU程序设计训练营清明天梯模拟赛题解

感谢大家今天来做题

比赛地址:http://202.206.177.79/contest/8

由于博主比较菜,没做完所有题目,这里暂时仅提供前两部分的题解。

为了节约篇幅,题目及数据描述不再赘述,如有需求,请移步OJ查看。

感谢大家的辛苦付出,但是从这次比赛的结果来看,前行之路还非常非常漫长呐。

趁机安利一个课程,比较适合大家学:北京理工大学ACM冬季培训课程

我寂寞的时候,会害怕踏出第一步。不会想到要去做什么事,所以,可能没有发觉很多很多的东西吧。——《夏目友人帐》

第一阶段

L1-1 天梯赛座位分配 (20分)

通过率:2.56% \(\;\;\) 通过人数:1

模拟题,整体难度大于后面几道题。此外对输出格式要求极为严苛,比较坑。

比较优秀的特点是数据范围不大,所以可以直接用 \(O(10*N*M)\) 的时间模拟编号过程(细节见代码)。

在每个学校都还有余量时,相临两位选手的座位号是以 \(N\) 为公差的等差数列,

但一旦有一些学校满编了,我们要在模拟的过程中跳过该校(要注意下 \(n\) 的后继是 \(1\)),

如果检测到当前只有一所学校有余量,需要隔座编号,我的代码使用了 \(add\) 累计隔座数量。

细心的同学可以发现输出描述有一句:行首尾不得有多余空格

这就比较魔性了,一般的题目行尾是可以有空格的,所以输出时格外注意格式(我第一次交就没看到这个)。

代码如下:

#include <bits/stdc++.h>
#define MAXN 107
using namespace std;
int n,pos,add,tot,p[MAXN];
vector<int> v[MAXN];
int main() {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		scanf("%d",&p[i]),p[i]*=10;
		tot+=p[i];
	}
	for (int i=1;i<=tot;i++) {
		int pre=pos; 
		pos=(pos==n)?1:pos+1;
		while ((int)v[pos].size()==p[pos])
			pos=(pos==n)?1:pos+1;
		if (pre!=pos) v[pos].push_back(i); //正常编号
		else v[pos].push_back(i+(++add)); //仅剩一所学校有余量,要隔座
	}
	for (int i=1;i<=n;i++) {
		printf("#%d\n",i);
		for (int j=0;j<p[i];j++) {
			printf("%d",v[i][j]);
			putchar((j+1)%10?' ':'\n'); //行尾不能有空格
		}
	}
	return 0;
} 

L1-2 倒数第N个字符串 (15分)

通过率:36.84% \(\;\;\) 通过人数:14

模拟题,用C++写应该比较简单,我头铁用Python搞了半天。

模拟的过程和数学上的加减法的进位/借位比较像,以从倒着数为例:

zba 的前驱是 zaz ,模拟的操作就是从后往前找到第一个不为a的字母,

然后把它之后的的所有a(如果有的话)改成z,计数器同时工作即可。

代码如下:

import math

def main():
    l, n = input().split()
    l = int(l); n = int(n)
    ans = 'z' * l
    for i in range(n-1):
        pos = l - 1
        while ans[pos]=='a': pos-=1
        ans = ans[:pos] + chr(ord(ans[pos])-1) + (l-pos-1)*'z'
    print(ans)
    
if __name__ == "__main__":
    main()

L1-3 打折 (5分)

通过率:73.91% \(\;\;\) 通过人数:34

考察了最简单的数据处理以及读入输出,注意一下整形到浮点形的运算语法以及保留小数即可。

代码如下:

def main():
    a, b = input().split()
    ans = float(a)*float(b)/10
    print("%.2f" %ans)

if __name__ == "__main__":
    main()

L1-4 2018我们要赢 (5分)

通过率:63.46% \(\;\;\) 通过人数:33

这题通过率和通过人数还不如上一题,咋肥事啊。

直接输出!你敢送分我敢拿!

代码如下:

def main():
    print("2018")
    print("wo3 men2 yao4 ying2 !")

if __name__ == "__main__":
    main()

L1-5 电子汪 (10分)

通过率:59.62% \(\;\;\) 通过人数:31

考察了作为单身狗的口算水平,也没什么好说的。

代码如下:

def main():
    a, b = input().split()
    for i in range(int(a)+int(b)):
        print("Wang!", end='')
        
if __name__ == "__main__":
    main()

L1-6 福到了 (15分)

通过率:18.97% \(\;\;\) 通过人数:11

模拟题,细节要求比较多。

题目所要求的倒置指的是整图旋转 \(180°\),满足的性质是 \(map[i][j] -> map[n-i+1][n-j+1]\)

简单观察一下样例可以知道,把列倒过来,再把行倒过来就倒置了。

判断是否输出bu yong dao le可以使用上面那个性质,如果每一个坐标都满足就要输出。

代码如下:

def main():
    k, n = input().split()
    n = int(n)
    l = []; r = []
    for i in range(n):
        opt = input().replace('@', k).ljust(n, ' ') #如果没有满的要用空格补全n
        l.append(opt)
    for i in range(n-1, -1, -1):
        t = list(l[i]).reverse()
        r.append(l[i][::-1])
    fl = 0
    for i in range(n):
        if l[i] != r[i]:
            fl = 1; break #发现有不满足性质的各自,标记一下不用输出
    if fl == 0: print("bu yong dao le")
    for i in range(n):
        for j in range(len(r[i])):
            if r[i][j]!=' ': print(k, end='')
            else: print(' ', end='')
        print('\n', end='')
        
if __name__ == "__main__":
    main()

L1-7 谁是赢家 (10分)

通过率:38.89% \(\;\;\) 通过人数:28

考察了判断语句,观众票数和评委投票的权重不同,其中得某人到全部评委的认可是一个强条件,

在不符从该条件的情况下以观众票数较多者为优。

代码如下:

def main():
    pa, pb = input().split()
    pa = int(pa); pb = int(pb)
    l = input().split()
    va = 0
    for i in range(3):
        if l[i] == '0': va += 1
    if va == 3 or (va > 0 and pa > pb): 
        print("The winner is a: %d + %d"%(pa, va))
    else: print("The winner is b: %d + %d"%(pb, 3-va))

if __name__ == "__main__":
    main()

L1-8 猜数字 (20分)

通过率:34.37% \(\;\;\) 通过人数:22

根据题意,先要算平均数的一半,然后找出分数最接近这个值的选手。

\(minn\) 记录了当前最小的差值绝对值,\(winter\) 在线更新较优选手名字。

代码如下:

def main():
    name = []; socre = []
    n = int(input()); tot = 0
    for i in range(n):
        t1, t2 = input().split()
        t2 = int(t2); tot += t2
        name.append(t1); socre.append(t2)
    average = int(float(tot/n)/2)
    winter = ""; minn = 2147483647
    for i in range(n):
        if abs(socre[i] - average) < minn:
            minn = abs(socre[i] - average)
            winter = name[i]
    print("%d %s"%(average, winter))
    
if __name__ == "__main__":
    main()

第二阶段

L2-1 分而治之 (25分)

通过率:50.00% \(\;\;\) 通过人数:2

简单的图论题,由于拆边比较麻烦,可以先离线记录每条边信息。

对于每个方案,用一个数组 \(vis\) 记录所打击的城市编号,之后遍历所有边,

只要有任一边合法(即两端城市都没有被打击),则该方案无效。

代码如下:

#include <bits/stdc++.h>
#define MAXN 10007
using namespace std;
struct Edge { int u,v; }; 
vector<Edge> E;
int n,m; bool vis[MAXN];
inline int readint() {
	int w=0,X=0; char ch=0;
	while(!isdigit(ch)) w=ch=='-',ch=getchar();
	while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
	return w?-X:X;
}
inline bool judge() {
	memset(vis,0,sizeof(vis));
        //vis数组标记打击的城市编号
	int Np=readint();
	for (int i=1;i<=Np;i++) vis[readint()]=true;
	for (int i=0;i<m;i++)
		if (!vis[E[i].u] && !vis[E[i].v]) //如果某条路两端都没有被打击,则方案无效
			return false;
	return true;
}
int main() {
	n=readint(),m=readint();
	for (int i=1;i<=m;i++) 
		E.push_back(Edge{readint(),readint()});
	int k=readint();
	while(k--) puts(judge()?"YES":"NO");
	return 0;
} 

扩展阅读:

[算法总结]并查集

L2-2 小字辈 (25分)

通过率:66.67% \(\;\;\) 通过人数:2

考察了图的存储和遍历,我这里使用的是 \(vector\) 存图,推荐大家也使用这种。

为了方便数据处理(数组下标需小于等于 \(0\),老祖宗的父节点下标为 \(-1\)),我把所有下标暂时 \(+1\),输出时再 \(-1\)

在读入的过程中,从父节点向子节点连一条边,完成建图。

\(dfs()\) 函数的作用是依次给每个节点附上辈分值,顺便求出最小的辈分。

代码如下:

#include <bits/stdc++.h>
#define MAXN 100007
using namespace std;
int n,book,dep[MAXN];
vector<int> son[MAXN];
inline int readint() {
	int w=0,X=0; char ch=0;
	while (!isdigit(ch)) w=ch=='-',ch=getchar();
	while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
	return w?-X:X;
}
void dfs(int u) { //图的深度遍历
	book=max(book,dep[u]);
	for (int i=0;i<(int)son[u].size();i++) {
		dep[son[u][i]]=dep[u]+1;
		dfs(son[u][i]);
	}
} 
int main() {
	n=readint();
	for (int i=1;i<=n;i++) {
		int u=readint();
		son[u+1].push_back(i+1); 
	}
	dep[0]=0,dfs(0);
	printf("%d\n",book);
	for (int i=1;i<=n;i++)
		if (dep[i+1]==book) printf("%d ",i);
	return 0;
} 

扩展阅读:

图的几种存储方式

图的深度遍历和广度遍历

L2-3 名人堂与代金券 (25分)

通过率:50.00% \(\;\;\) 通过人数:1

一道看起来不难的模拟题,实际上细节要求非常多,要把各种情况想到位。

首先推荐使用一个结构体存储每个学生的信息,然后按分数从大到小排序,如有同分,则按照题意以账号字母升序排。

对于第一个问题,即求发放代金卷的总面值,直接累加后输出即可。

对于输出排名,要注意同分者排名是并列的,我们使用 \(cnt\) 记录真实排名,一旦发现与前人分数不同,则用 \(i\) 矫正。

还有一种方法是给结构体赋排名值,这样应该写起来更简单些,但思路差不多。

代码如下:

#include <bits/stdc++.h>
#define MAXN 100007
using namespace std;
struct student {
	string id; int soc;
	bool operator < (const student &T) {
		return (soc>T.soc)||(soc==T.soc && id<T.id);
                //两个排序关键字,优先以分数排序,分数相同时以账号排升序
	}
}s[MAXN];
int n,g,k,cnt=0,sum=0;
int main() {
	cin>>n>>g>>k;
	for (int i=1;i<=n;i++) {
		cin>>s[i].id>>s[i].soc;
		if (s[i].soc>=g) sum+=50; 
		else if (s[i].soc>=60) sum+=20;
	}
	cout<<sum<<'\n'; sort(s+1,s+n+1);
	for (int i=1;i<=k;i++) {
		if (s[i].soc!=s[i-1].soc) cnt=i; //cnt控制真实名次
		cout<<cnt<<' '<<s[i].id<<' '<<s[i].soc<<'\n';
	}
	for (int i=k+1;i<=n;i++) {
		if (s[i].soc!=s[i-1].soc) break;
		cout<<k<<' '<<s[i].id<<' '<<s[i].soc<<'\n';
	}
	return 0;
}

L2-4 秀恩爱分得快 (25分)

通过率:33.33% \(\;\;\) 通过人数:1

下面的代码有点长,其实写的时候很多内容是可以 Ctrl + V 的。

仍是一道模拟题,题目中的关键要素有:亲密度、亲密值、性别。

首先观察到用尽管性别用正负数区分,但是其绝对值是没有重复的,所以考虑用单独一个数组 \(sex\) 记录性别,然后可以将所有编号全部取绝对值。

由于只关心两位主人公对于其他的亲密值,所以可以先记录下照片信息,然后离线统计,这样就只需要两个一维数组就可以保存我们所需的所有信息。

在离线读取每张照片的信息时,使用 \(fa\)\(fb\) 标记两位主人公是否有出现,如有出现,则按题目所给公式累计对其余人的亲密值。

不定数组 \(la\)\(lb\) 记录的就是当前两人亲密值最大的人的集合,保证对集合内容的人亲密值相等。

注意如果当前集合内只有对方,访问到一个亲密值相同的他人,则不予加入。

还有注意的是性别问题,同性可以忽略,异性才进行计算,具体细节请见代码。

代码如下:

#include <bits/stdc++.h>
#define MAXN 1007
#define eps 1e-7
using namespace std;
int n,m,a,b,k[MAXN],sex[MAXN];
double va[MAXN],vb[MAXN];
vector<int> p[MAXN],la,lb;
inline int readint() {
	int w=0,X=0; char ch=0;
	while (!isdigit(ch)) w=ch=='-',ch=getchar();
	while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
	return w?-X:X;
}
int main() {
	n=readint(),m=readint();
	for (int i=1;i<=m;i++) {
		k[i]=readint();
		for (int j=0;j<k[i];j++) {
			int opt=readint(); 
			if (opt>=0) sex[opt]=1,p[i].push_back(opt);
			else sex[-opt]=-1,p[i].push_back(-opt); //取编号绝对值
		}
	}
	a=abs(readint()),b=abs(readint()); //取编号绝对值
	for (int i=1;i<=m;i++) {
		bool fa=false,fb=false;
		for (int j=0;j<k[i];j++) {
			if (p[i][j]==a) fa=true;
			if (p[i][j]==b) fb=true;
		}
		for (int j=0;j<k[i];j++) {
			if (fa && p[i][j]!=a) va[p[i][j]]+=1.0/k[i]; //按照题目给出的公式计算亲密度
			if (fb && p[i][j]!=b) vb[p[i][j]]+=1.0/k[i];
		}
	}
	la.push_back(b),lb.push_back(a);
	for (int i=0;i<n;i++) {
		if (va[i]>va[la[0]]+eps && sex[a]*sex[i]<0) //eps控制浮点误差,sex确保性别
			la.clear(),la.push_back(i); //发现亲密值大于集合内的异性,先清空,后加入新人
		else if (abs(va[i]-va[la[0]])<eps && sex[a]*sex[i]<0 && va[la[0]]>va[b]+eps) 
			la.push_back(i); //发现一样的且集合内没有原配,则直接加入
		if (vb[i]>vb[lb[0]]+eps && sex[b]*sex[i]<0) 
			lb.clear(),lb.push_back(i);
		else if (abs(vb[i]-vb[lb[0]])<eps && sex[b]*sex[i]<0 && vb[lb[0]]>vb[a]+eps)
			lb.push_back(i);
	}
	if (la[0]==b && lb[0]==a) printf("%d %d",sex[a]*a,sex[b]*b); //简单输出控制
	else {
		for (int i=0;i<(int)la.size();i++) printf("%d %d\n",sex[a]*a,-sex[a]*la[i]);
		for (int i=0;i<(int)lb.size();i++) printf("%d %d\n",sex[b]*b,-sex[b]*lb[i]);
	}
	return 0;
}

以上。

posted @ 2020-04-04 21:53  暖暖草果  阅读(387)  评论(0)    收藏  举报