泥电睿智题目
写在前面
这些题解显然不是写给我自己看的。
这些题解都是尽力揣摩 \(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\) 进制容易许多。