• 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
    ",i);
    		for (int j=0;j<p[i];j++) {
    			printf("%d",v[i][j]);
    			putchar((j+1)%10?' ':'
    '); //行尾不能有空格
    		}
    	}
    	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('
    ', 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
    ",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<<'
    '; 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<<'
    ';
    	}
    	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<<'
    ';
    	}
    	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
    ",sex[a]*a,-sex[a]*la[i]);
    		for (int i=0;i<(int)lb.size();i++) printf("%d %d
    ",sex[b]*b,-sex[b]*lb[i]);
    	}
    	return 0;
    }
    

    以上。

  • 相关阅读:
    JavaScript快速入门-DOM对象
    JavaScript快速入门-ECMAScript对象介绍
    JavaScript快速入门-ECMAScript函数
    JavaScript快速入门-ECMAScript运算符
    [转] boost::function用法详解
    [转] [翻译]图解boost::bind
    [转] Git 分支
    [转] 多线程下变量-gcc原子操作 __sync_fetch_and_add等
    [转] Boost智能指针——scoped_ptr
    [转] linux下的c/c++调试器gdb
  • 原文地址:https://www.cnblogs.com/zhwer/p/12634446.html
Copyright © 2020-2023  润新知