泥电睿智题目

写在前面

这些题解显然不是写给我自己看的。
这些题解都是尽力揣摩 \(template\) 的用意以尽力符合新学者的代码习惯打的。请勿抄袭。

附一张群友发的图。

week6

A

思路

这道题只需要按操作模拟,对于四舍五入操作,可以通过 \(round\) 函数来实现。

Code

#include<stdio.h>   //标准库
#include<math.h>    //round函数在math库中

int main(){
	int cal, ip, anonymous; //credit
	int calgrade, ipgrade, anonymousgrade;  //grade
	scanf("%d%d%d", &cal, &ip, &anonymous); //输入,可以参考此例,注意变量名前需要有取址符&
	scanf("%d%d%d", &calgrade, &ipgrade, &anonymousgrade);
    double ans = 0;                         //double可暂时理解为等价与float,储存小数
    ans += cal*calgrade+ip*ipgrade+anonymous*anonymousgrade;        //计算分子
    ans /= (cal+ip+anonymous);              //计算结果
    int num = round(ans);                   //round(x)意为对x进行四舍五入
    printf("%d", num);                      //输出,需注意输出变量名前不需要&
    return 0;
}

B

思路

对于并联和串联被迫写个函数操作,然后欧姆定律就行,注意单位转换答案需 \(\times 1000\)

Code

#include<stdio.h>               //标准库

float series(float a, float b){     //计算两串联电阻阻值
    return a+b;
}

float parallel(float a, float b){   //计算两并联电阻阻值
    return a*b/(a+b);
}

float Ohm_law(float a ,float b){    //计算欧姆定律
    return a/b;
}

int main(int argc, char **argv){    //我也不知道括号里是什么,想学自己百度
	double r1, r2, r3, r4, r5, r6, r7;          //7个电阻定义
	scanf("%lf%lf%lf%lf%lf%lf%lf", &r1, &r2, &r3, &r4, &r5, &r6, &r7);
    double k = 12;                  //其实没有必要
    double left = series(r1, r2);   //图左侧电阻阻值
	double right = series(r5, parallel(r6, r7));        //右侧阻值
	double mid = parallel(r3, r4);                      //中间阻值
    double ans = 1000*Ohm_law(k, parallel(parallel(left, right), mid));         //答案为12除以左中右三者的并联,注意单位换算*1000
    printf("%.2lf", ans);                               //此处%.2lf意为保留两位小数(会自动四舍五入)
    return 0;
}

C

思路

直接从大到小找 \(root\) 来反求 \(num\),然后求出 \(reverse num\) 比较一下即可。

\(Code\)

//建议前置学习:递归(https://oiwiki.org/basic/divide-and-conquer/)也可以自己网上搜
#include <stdio.h>
#include <math.h>       //sqrt函数在math库中

long long reverseNumber(long long x) // This function will return a long integer reverse x. 
{
    long long revx = 0;             //定义一个数为x的翻转
    while(x) {                      //当x不为0时重复执行
        revx = revx * 10 + x % 10;  //翻转数*10+x末尾
        x /= 10;                    //x抹去最后一位
    }                               //这段while大家可以草稿纸上手写几个数操作一下
    return revx;
}

int findPalindromicRoot(long long root) //This function need to find the root of Largest Palindromic Square Number.
//这是一个递归实现的函数,是模板自带的递归(大概是认为大家都能无师自通吧)
{
	long long num = root*root;                  //定义一个数为root平方
	long long revnum = reverseNumber(num);      //通过reverse函数求得翻转后的数字
	if(revnum == num)	return root;            //如果反转前后相等则root可行
    return findPalindromicRoot(root - 1);       //否则继续查找root-1是否可行
}

int main()
{
	long long n;
	scanf("%lld", &n);                      //读入,int 为d,longlong为lld,float为f,double为lf
	long long root = sqrt(n);               //sqrt即为开根
	long long ans = findPalindromicRoot(root);
    printf("%lld %d\n", ans*ans, ans);
    return 0;
}

//这里函数的英文注释为模板自带,应该可以看懂函数的意义
//可以思考这样一个问题:为什么查找开根后的数字,而不是直接从n一个一个往小找直到找到最大的回文平方数。
//相关知识:时间复杂度(https://oiwiki.org/basic/complexity/)事实上这个相当重要,以后的题一定会用得到,可以做一些了解。

week 8

关于调代码,请务必首先检查你的代码是否能运行,不要拿着一份 compile error 的代码找别人问,除非你真的不知道你的代码为什么连运行都运行不了。另外请先尝试自行调试,调试能力的重要性不亚于写代码能力,毕竟没有人能保证一次写对。另外希望询问代码结构提问的时候你能说出某一段是用做什么,而不是chatgpt写的。欢迎任何调试的提问。

A

思路

这道题之前的自己写的思路实在是一坨,像大家诚挚的道歉。
这里对于所有数字,我们如果只考虑把末尾调到第一位,实际上是很简单的,即 \(m=res\times(n\%10)+(n/10)\)。那么就会出现以下情况:\(1234->4123->3412->2341->1234\),所以只需要持续进行这样的循环,并在出现一个数等于最开始的 \(n\) 时即终止循环。如果出现循环节如 \(1212\),那么在第一次出现 \(1212\) 后以后再查找到的数也一定是找到过的,同样也符合终止循环。
如果存在前导零,这看上去是十分难以处理的,但是代码中强制要求了由 \(n\) 出发寻找的答案 \(m\) 严格大于 \(n\),如果出现了任意前导零的情况,则可以发现找到的数一定将小于 \(n\)。如果反之的话,即若通过 \(n\) 寻找到的 \(m\) 严格小于 \(n\),则虽然不会重复但是会出现计入有前导零的情况。
这道题用了挺多的 trick,可以认真思考一下为什么这样做。(我也没想到才写出了之前两版沟槽的思路捏)

Code

#include <stdio.h>

int highestOrder(int x) // This function is used to calculate the highest order of a number x. 
//e.g. if x = 12345, then the res should be 10000; x = 7890, then res = 1000.
{
    int res = 1; 
    while(x >= 10){
        res *= 10;
        x /= 10;
    }
    return res; 
}

int main(){
    int A, B; 
    int ans = 0;
    scanf("%d %d", &A, &B);
    for(int i = A; i <= B; ++i){
        int n = i;                              //这里记录n为当前查找的数
        int res = highestOrder(i);              //找到提示中提到的res
        n = res*(i%10)+(i/10);                  //寻找把末尾调整到首位后的结果
        while(n != i){                          //只要还没有进入循环
            if(i < n && n <= B)    ++ans;       //如果i满足比查找的数大并且小于等于上界B,++ans
            n = res*(n%10)+(n/10);              //重新找到由当前的n找到的下一个数
        }
    }
    printf("%d\n", ans);
    return 0; 
}

B

思路

题面很有意思,是一道交互题。
什么是交互题?即非传统的输入输出方式,有问答环节,你可以通过输出询问获取信息。这里注意,交互题有严格的输出格式限制,请认真阅读题目中的输出提示,否则很难通过评测。
这里的思路更是哈哈大笑,题面虽然只给了 \(30\) 次询问,却告诉你根据概率学 \(30\) 次大概率蒙到正确答案,所以你只需要随便选这么多个数,计算一下相互的差值如何进行一个最大公约数的求即可。
严格的说,这绝对不是一道严谨的题目,可以显然的见得仍然有小概率出现的求出错误解的情况,比如每次都抽到 \(2a\) 倍数差的点而非 \(a\)。当然这样的概率极小,\(2^{-29}\)。然而小概率并不是这道题不哈哈大笑的理由。
一些试错:这道题必须使用 \(rand()\),因为 \(code structure\) 会测你的码看你有没有 \(rand()\)

Code

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int n, m;
int a[35], cnt;

int gcd(int a, int b){              //gcd意味最大公约数,此处的方法为辗转相除法。
    if(b == 0) return a;
    return gcd(b, a % b);
}

int main(){
	srand(time(NULL));              //这句话为用rand种子为与时间有关,若无此句,则所有rand的输出都是一致的
	scanf("%d%d", &n, &m);
	a[++cnt] = m;
	for(int i = 1; i <= 30; ++i){   //很显然越多的询问是越好的,所以直接询问最大次数 30 次
		printf("? %d\n", rand()%n); //随意询问一个 0~n-1的随机数
		fflush(stdout);
		scanf("%d", &m);            //输入a[rand]
		a[++cnt] = m;
	}
	int num = abs(a[1]-a[2]);
	for(int i = 1; i <= cnt; ++i){
		for(int j = i+1; j <= cnt; ++j){
			if(a[i] == a[j])	continue;           //如果询问了两次相同的数,即差为0,应提前判断去除,否则gcd中将%0报错
			else	num = gcd(num, abs(a[i]-a[j]));
		}
	}
	printf("! %d", num);
    return 0; 
}

C

思路1

同样也是很有趣的一道题目。禁止用乘法除法与异或。
一点无关的知识:这是一棵树(无向无环联通图)。特别的,这是一棵二叉树,即每个父节点都有两个儿子,左边为左儿子,右边为右儿子。更特别的,这棵树的节点编号有这样的性质,对于父节点 \(u\),左儿子为 \(u<<1\),右儿子为 \(u<<1|1\),这是一个常用结论。
What is Xor?Xor 即异或,指两个数在二进制拆分后,对于每一位,进行同则取 \(0\),异则取 \(1\) 的运算。
做这道题前请先阅读 2.3,这将很好的帮助你构建本题的思路。这里先提供一条可行思路,对于输入数,每次右移一位即可得到它的父节点,一直用自己写的异或函数计算即可。

思路2

已知任意逻辑运算符都可以用基础逻辑运算符与或非表示,因此可以使用与或非表示异或,这样会简单许多,这里的异或即 \(A xor B=(!A\&B)|(A\%!B)\)

Code1

#include<stdio.h>
#include<stdbool.h>

long long n;

bool ca[65], cb[65], cans[65];             //二进制拆分记录数组

long long max(long long a, long long b){
  return a > b;
}

long long XOR(long long a, long long b){
	memset(ca, 0, sizeof(ca));
	memset(cb, 0, sizeof(cb));
	memset(cans, 0, sizeof(cans));        //memset这几句为把数组归零
	int cnta = 0, cntb = 0;              //记录a,b二进制位数
	while(a){                            //两个while分别维护a、b二进制拆分
		ca[++cnta] = a%2;
		a >>= 1;
	}
	while(b){
		cb[++cntb] = b%2;
		b >>= 1;
	}
	for(int i = 1; i <= max(cnta, cntb); ++i)
		if((ca[i]&&cb[i]) || (!ca[i]&&!cb[i]))	cans[i] = 0;      //如果相同就为0,不同就为1
		else	cans[i] = 1;
	long long now = 0;
	for(int i = max(cnta, cntb); i >= 1; --i)	now = (now<<1)+cans[i];      //还原
	return now;
}

int main(){
	scanf("%lld", &n);
	long long ans = n;
	while(n){
		ans = XOR(ans, n>>1);          //n>>1即为n/2
		n >>= 1;
	}
	printf("%lld", ans);
	return 0;
}

Code2

#include<stdio.h>

long long n;

long long XOR(long long a, long long b){
    return (a&(~b))|((~a)&b);          //这里为异或的与或非表达
}

int main(){
    scanf("%lld", &n);
	long long ans = n;
	while(n){
		ans = XOR(ans, n>>1);          //n>>1即为n/2
		n >>= 1;
	}
	printf("%lld", ans);
	return 0;
}

week 12

感觉比前面几周简单一些。

A

思路

只需要定义一个二维数组记录每一个点是否在范围内,然后在结束的时候扫描一遍全图统计即可。时间复杂度 \(m\times r^2\)
当然,这里有一道非常经典的题目————扫描线,给出一些矩形有重区域,求面积,运用线段树可以做到 \(mr\log r\)

B

思路

这里可以发现一个简单的性质,只需要将斜对角线的数交换即可。

C

思路

基本功题目,思路大家都会。这里有一个 trick,在求 \(16\) 进制的时候,可以传 \(2\) 进制字符数组,每 \(4\) 位一分作为一个 \(16\) 进制位,这样会比直接用 \(10\) 进制容易许多。

posted @ 2024-10-12 19:21  Zzzzzzzm  阅读(478)  评论(0)    收藏  举报