A、装水
- 操作 1:手动加水(需最小化);
- 操作 2:移动水(无次数限制,可跨空段移动);
- 自动填充:若空单元格
i(2≤i≤n-1)左右都有水,会自动填满; - 关键推论:只要存在≥3 个连续的空单元格,仅需 2 次操作 1 就能填满所有空单元格(其余空单元格可通过 "自动填充 + 操作 2 移动水" 覆盖);若不存在≥3 个连续空单元格,则每个空单元格都需手动加 1 次水。
cpp
#include<stdio.h>
#include<stdlib.h>
int main() {
int n;
scanf("%d", &n);
while (n--) {
int m;
scanf("%d", &m);
char* str = (char*)malloc((m+5) * sizeof(char));
scanf("%s", str);
int w = 0, count = 0;
for (int i = 0; i < m-2; i++){
if (str[i] == '.' && str[i + 1] == '.' && str[i + 2] == '.') {
w = 1;
break;
}
}
for (int i = 0; i < m; i++) if (str[i] == '.')count++;
if (w == 1){
printf("2\n");
continue;
}
else printf("%d\n", count);
}
return 0;
}
B、签到签到
这个问题的核心是双指针法(夹逼法),利用 "x²+y² 随 x 增大、y 减小的单调性",在 O (√D) 时间复杂度内找到使 | x²+y²-D | 最小的非负整数对 (x,y),避免暴力枚举所有可能的 x、y(暴力枚举会超时,因为 D 最大到 1e12,√D 约 1e6,双指针法效率极高)。
核心原理
对于非负整数 x、y:
- x²+y² 的取值范围:最小为 0(x=y=0),最大无界,但我们只需要找到最接近 D 的取值;
- 单调性:固定 x 时,y 越大,x²+y² 越大;固定 y 时,x 越大,x²+y² 越大;
- 双指针策略:
- 初始时,x 从 0 开始(最小),y 从√D 开始(最大的可能使 y²≤D 的数);
- 通过调整 x 和 y 的大小,逐步逼近 "x²+y²=D" 的状态,过程中记录最小差值。
cpp
#include<stdio.h>
#include<math.h>
int main() {
long long D;
scanf("%lld", &D);
long long x = 0;
long long y = (long long)sqrt(D);
long long min_diff = D;
while (x <= y) {
long long cur = x * x + y * y;
long long diff = cur - D;
if (diff < 0) {
diff = -diff;
}
if (diff < min_diff) {
min_diff = diff;
}
if (cur > D) {
y--;
}
else {
x++;
}
}
printf("%lld\n", min_diff);
return 0;
}
C、热爱物理的zwz
这个问题的核心是利用 "光的全反射对称原理" 推导激光的入射向量,再通过 "最大公约数(GCD)约分" 保证输出向量的最简形式(gcd (i,j,k)=1)。
物理反射原理(核心推导)
-
坐标系定义:
- zwz 位置为原点 (0,0,0),水面是 z=h 的平面(zwz 在水下,距离水面高度为 h,因此水面的 z 坐标是 h);
- 目标位置为 (x,y,z),激光需先射到水面,经一次全反射后到达目标。
-
反射的 "镜像法"(关键!):光的反射可等效为 "激光沿直线射向目标的镜像点",镜像点的计算规则:
- 水面是 z=h,目标点 (x,y,z) 关于水面的镜像点 z 坐标为:
h + (h - z) = 2h - z(x、y 坐标不变); - 因此,激光的入射向量就是从原点 (0,0,0) 指向镜像点 (x, y, 2h - z) 的向量,即初始向量为
(x, y, 2h - z)。
👉 样例验证:样例输入 h=5,目标 (3,3,2) → 镜像点 z 坐标 = 2*5 -2=8 → 初始向量为 (3,3,8)(与样例输出一致,且该向量 gcd=1,无需约分)。
- 水面是 z=h,目标点 (x,y,z) 关于水面的镜像点 z 坐标为:
cpp
#include<stdio.h>
int main()
{
int n, a,b,c;
scanf("%d", &n);
scanf("%d %d %d", &a,&b,&c);
int u, uu;
if (n != 0)
u = n - c + n;
else
u = c;
if (a < b) {
uu = a;
}
else {
uu = b;
}
if (uu > u) {
uu = u;
}
for (int i = uu; i >= 2; i--) {
if (a % i == 0 && b % i == 0 && u % i == 0) {
a = a / i;
b = b / i;
u = u / i;
i++;
}
}
printf("%d %d %d", a, b, u);
return 0;
}
D、学的太少了,水题凑一下好了
cpp
#include<stdio.h>
int num(int a, int b) {
if (b == 0) return a;
else return num(b, a % b);
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
int t = num(n, m);
printf("%d", t);
}
E、奇怪的等式
这个问题的核心是通过数学公式构造满足条件的整数解 (x,y,z),而非暴力枚举(暴力枚举会因 n 范围大而超时),同时对特殊值(n=0、n=1)直接判定无解。
关键数学推导(构造解的核心)
题目要求找到互不相同的整数 x,y,z,满足:x1+y1+z1=n2
步骤 1:简化问题,先固定两个变量
我们尝试构造一组通用解,选择x = n(第一个变量),代入等式:n1+y1+z1=n2化简得:y1+z1=n1
步骤 2:构造 y 和 z 的解
对 y1+z1=n1,我们需要找两个互不相同的整数 y、z,且与 x=n 也互不相同。经典的分数拆分技巧:令 y=n+1(第二个变量),代入得:z1=n1−n+11=n(n+1)(n+1)−n=n(n+1)1因此 z=n(n+1)(第三个变量)。
步骤 3:验证构造的解是否满足所有条件
构造的解为:
- x=n
- y=n+1
- z=n(n+1)
验证等式:n1+n+11+n(n+1)1=n(n+1)(n+1)+n+1=n(n+1)2n+2=n(n+1)2(n+1)=n2完全满足原式!
同时验证 "互不相同":
- n ≥ 2 时,n <n+1 < n (n+1)(如 n=3 时,3 < 4?不,样例中 n=3 时 y=7?哦,样例是特殊构造,核心逻辑是 "存在通用构造解",样例的解是另一组有效解,代码的构造解是更通用的)。
特殊值判断(n=0、n=1 无解)
- n=0:原式右边 02 无意义,直接判定无解;
- n=1:代入构造解得 x=1、y=2、z=2,z 与 y 重复(不满足 "互不相同"),且无其他有效解,判定无解;
- n≥2:构造的 x=n、y=n+1、z=n (n+1) 互不相同,且满足等式,因此有解。
cpp
#include <stdio.h>
int main ()
{
int n,x,y,z;
scanf("%d",&n);
x=n;
y=n+1;
z=(n+1)*n;
if(n==0||n==1)
{
printf("小王大王");
}else
{
printf("%d %d %d",x,y,z);
}
return 0 ;
}
F、路径
这个问题的核心是动态规划(DP)求解网格最短路径数,结合 "只能向右 / 向下走(最短路径的唯一走法)" 的规则,通过状态转移方程递推所有路径数,最终对结果取模。
问题本质(最短路径的约束)
网格中从起点 (0,0) 到终点 (n-1,m-1) 的最短路径,只能由 "向右(→)" 和 "向下(↓)" 两种移动组成(任何向左 / 向上的移动都会增加路径长度)。
- 最短路径的总步数固定:需要走
(n-1)次向下 +(m-1)次向右 =n+m-2步; - 路径数等价于:在
n+m-2步中选择n-1步向下(或m-1步向右)的组合数,即 C(n+m−2,n−1)(组合数公式)。
cpp
#include<stdio.h>
#include<math.h>
#define N 2000
int a[N][N];
int main() {
int n, m, i, j;
scanf("%d%d", &n, &m);
for ( i = 0; i <N; i++)
{
a[i][0] = 1;
a[0][i] = 1;
}
for ( i = 0; i <n; i++)
{
for ( j = 0; j <= m; j++)
{
a[i + 1][j + 1] = (a[i][j + 1] + a[i + 1][j]) % 1000000007;
}
}
printf("%d", a[n-1][m-1]);
return 0;
}