• CSDN竞赛57期题解


    总结

    交卷时一看才六十多分还有点吃惊,一看非编程题部分还是丢了二十分。填空题是这类竞赛最大的诟病,答案是名词的必然不唯一,答案需要计算的给定的参考答案必然计算错误,更离谱的是题目出成这样,反馈后官方竟然一点改变的意思都没有。但凡答案不唯一的题目你给多个候选答案也不会被人这么吐槽了。寄存器与状态寄存器、图灵机与图灵机模型这种不唯一的答案还记忆犹新,多少期前反馈时,C站不想着手动改一下判错的答案,而是回复以后不会再出填空题了,然而仅隔了一期填空题又再现了。

    为什么这类竞赛的赞助商这么钟爱填空题呢?比如这次填空题答案是BST,看到题目时候我就在群里艾特出题人,BST至少有三种中文叫法,你想让我们填哪种?还特地翻了下书,没找着书上的叫法,于是在二叉搜索树、二叉排序树、二叉查找书当中选了个最常见的二叉搜索树填上去了,果不其然答案是二叉排序树,十分丢失。

    另一道扣分的是判断题,问树的等价二叉树是不是唯一的,我寻思按照正常的转换方法应该是唯一的,结果却不是。失分的两题在书上也都没翻到解答。回到开始的问题,为什么赞助商总是出这种让人反感的填空题,他们的回复是以书上答案为准。提供了赞助,用这种方式迫使我们买本书不过分吧。还是随遇而安,叫不醒装睡的人,能白嫖本书也不亏。

    编程题部分几乎该类竞赛每期都出现了不给数据规模的情况,可以发现出题人相当的不专业,python选手用map切分下就好了,C++选手每次写之前还要手动处理下输入数组,很是烦人。另外这次的两道题都不给数据范围,全靠自己猜范围了。

    题目列表

    1.凑数

    题目描述

    给定一组n个正整数,要求每次选其中一个数乘以或除以一个素数(称为一次凑数),问至少需要凑数多少次可以把所有的数都凑成相等。

    分析

    将所有数凑成相等第一反应是凑成最小公倍数或者最大公约数。但是这种方式的解很容易被推翻,比如2 4 8,凑成2和8的操作次数都是3,但是凑成4的次数是2,那么4这个数有什么性质呢?显然作为最终凑成的数,数组中小于它的数一定是它的约数,大于它的数一定是它的倍数。所以我们可以暴力求解一波,先列出一波候选解,除了数组里每个元素,还有就是它们的最大公约数和最小公倍数。可以先打表求出正向前 i i i个元素的最小公倍数 x x x以及逆向后 n − i n - i ni个元素的最大公约数 y y y,如果 x x x 能被 y y y整除就尝试将 x x x y y y作为候选解求下凑的次数,最后取个最小值就可以了。

    当然比赛时我没有这么做,秉着最大得分原则,读完题就想着先混个部分分,求出所有数的最大公约数 t t t,然后求所有数到 t t t需要操作的次数之和。提交通过了大部分用例,数据范围改到十万再提交就AC了,可见这个题目的数据还是比较水的,有点数论基础的同学都可以比较快的AC。

    代码

    #include 
    #include 
    using namespace std;
    int a[100005];
    int gcd(int a, int b) {
    	return b ? gcd(b, a % b) : a;
    }
    int get(int n) {
    	int res = 0;
    	for(int i = 2; i <= n; i++) {
    		while(n % i == 0) {
    			n /= i;
    			res++;
    		}
    	}
    	return res;
    }
    int main() {
    	string s;
    	getline(cin,s);
    	int n = 0;
    	int t = 0;
    	int sz = s.size();
    	for(int i = 0; i < sz; i++) {
    		if (s[i] != ' ') t = t * 10 + s[i] - '0';
    		else {
    			a[n++]=t;
    			t = 0;
    		}
    	}
    	a[n++] = t;
    	t = a[0];
    	for (int i = 1; i < n; i++) {
    		t = gcd(t, a[i]);
    	}
    	int res = 0;
    	for(int i = 0; i < n; i++) {
    		int r = a[i] / t;
    		res += get(r);
    	}
    	cout<<res<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    2.树的寻路

    题目描述

    给定一棵有n个节点且节点编号为1到n的树,求满足以下条件的路径组合数: 1. 从节点a到节点b的路径(称为路径ab) 边数为p 2. 从节点c到节点d的路径(称为路径cd)边数为q 3. 路径ab和cd不交,即不存在一个节点既在路径ab又在路径 cd上

    分析

    这类题目早几年做应该是可以秒掉的,工作躺平太久加之下班比较困了,花了挺长时间才通过四成用例。首先题目用例给了三条边,结果是8,没有用例说明还是让人很疑惑的,毕竟组合数这个词比较模糊。用例是1到2,2到3,3到4这三条边,乍一看不就1-2和3-4这两个长度为1的边不相交嘛,就算再倒过来算上3-4和1-2也才两种组合数啊,8是怎么得到的?不妨尝试去枚举一下路径的起点,第一条路径的起点可以是1 2 3 4,可以得到以下的路径组合:

    • 1-2,3-4
    • 1-2,4-3
    • 2-1,3-4
    • 2-1,4-3
    • 3-4,1-2
    • 3-4,2-1
    • 4-3,1-2
    • 4-3,2-1

    虽然看起来这些组合有点离谱,就两条路径弄出了八个组合,但是用例来看应该就是这种组合方式了。

    想象一下作为一棵树,其中的任意一个节点作为路径的起点,路径可以向其孩子节点延伸,也可以向其祖先节点延伸,也可以通过祖先节点向其兄弟、表兄弟节点延伸,我们选定一个根节点来遍历是没有什么意义的。

    不妨先建个图,然后像上面枚举路径起点那样去枚举路径长度为 x x x路径的起点,遍历到其中一条长度为 x x x的路径的终点时,要保证遍历途中的节点做好了标记。再遍历下所有节点,只要没被标记的节点都可以作为与长度为 x x x路径不相交的、长度为 y y y的路径的起点。再次以新的起点去遍历相邻节点,直到找到与第一条路径不相交的路径,就将方案数加上1。

    dfs过程中要注意遇见被标记的节点不可继续拓展,因为要么是第二条路径遍历的时候遇见的第一条路径上的节点,要么就是父节点,从 a a a走到 b b b,显然不能再走回来了。

    这题没有给定数据范围,比赛时就假定数据范围是1w了,题目评测是一旦有超时的用例就是TLE,一分没有。所以加了下数据规模超过一千的就输出0。当然直接输出0也能得到一成的分数。比赛时由于时间关系没有继续调整数据范围和TLE的界限的,不然应该可以通过更多的用例。

    代码

    #include 
    #include 
    #include 
    using namespace std;
    const int N = 10005, M = 2 * N;
    int idx = 0,e[M],ne[M],h[N];
    int n,x,y,ans = 0;
    int st[N];
    void add(int a,int b) {
    	e[idx]=b, ne[idx]=h[a],h[a] = idx++;
    }
    void dfs(int u, int d, int t) {
    	if (!t) {//枚举的是第一条路径
    		if (d == x) {//路径长度达到x
    			for(int i = 1; i <= n; i++) {
    				if (!st[i]) {//枚举第二条路径的起点
    					st[i] = true;
    					dfs(i, 0, 1);
    					st[i] = false;
    				}
    			}
    			return;
    		}
    	} else {//枚举的是第二条路径
    		if (d == y) {//第二条路径长度达到y
    			ans++;
    			return;
    		}
    	}
    	for(int i = h[u]; ~i; i = ne[i]) {
    		int j = e[i];
            //已经被标记的节点跳过
    		if (st[j]) continue;
    		st[j] = true;
    		dfs(j, d + 1, t);
    		st[j] = false;
    	}
    }
    int main() {
    	cin>>n>>x>>y;
    	memset(h,-1,sizeof h);
    	for(int i = 1; i < n; i++) {
    		int a,b;
    		cin>>a>>b;
    		add(a,b);
    		add(b,a);
    	}
    	if (n > 1000) {
    		cout<<0<<endl;
    		return 0;
    	}
    	for(int i = 1; i <= n; i++) {
    		st[i] = true;
            //枚举长度为x的路径起点i
    		dfs(i, 0, 0);
    		st[i] = false;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    基于 Apache Hudi 和 Apache Spark Sql 的近实时数仓架构分享
    java泛型机制详解篇二(深入理解泛型篇一)
    人体姿态估计(人体关键点检测)2D Pose训练代码和Android源码
    GitHub上线重量级分布式架构原理设计笔记,开源的东西看着就是爽
    SSH远程连接实例
    读书笔记:Effective C++ 2.0 版,条款28(namespace )
    java毕业设计火车订票管理系统mybatis+源码+调试部署+系统+数据库+lw
    Vscode行尾序列LF和CRLF
    MYSQL中ORDER BY(排序查询)
    机器学习笔记:自监督学习
  • 原文地址:https://blog.csdn.net/qq_30277239/article/details/131139668