我们可以这么理解,你有两个杯子 a a a 和 b b b,两个杯子里都盛满了水,现在想把两个杯子里的水交换一下,那么第一个想到的方法是什么?
当然是再找来一个临时杯子:
1)先把 a a a 杯子的水倒进这个临时的杯子里;
2)再把 b b b 杯子的水倒进 a a a 杯子里;
3)最后把临时杯子里的水倒进 b b b 杯子;
这种就是临时变量法,那么当然,还有很多很多的方法,接下来就让我们来见识一下吧。
三、代码详解
1、正确解法1:引入临时变量
代码语言:javascript
复制
#include <stdio.h>
int main() {
int a, b, tmp;
while (scanf("%d %d", &a, &b) != EOF) {
tmp = a; // (1)
a = b; // (2)
b = tmp; // (3)
printf("%d %d\n", a, b);
}
return 0;
}
( 1 ) (1) (1) tmp = a;表示把 a a a 杯子的水倒进这个临时的杯子里;
( 2 ) (2) (2) a = b;表示把 b b b 杯子的水倒进 a a a 杯子里;
( 3 ) (3) (3) b = tmp;表示把临时杯子里的水倒进 b b b 杯子里;
这三步,就实现了变量 a a a 和 b b b 的交换。
2、正确解法2:引入算术运算
代码语言:javascript
复制
#include <stdio.h>
int main() {
int a, b;
while (scanf("%d %d", &a, &b) != EOF) {
a = a + b; // (1)
b = a - b; // (2)
a = a - b; // (3)
printf("%d %d\n", a, b);
}
return 0;
}
( 1 ) (1) (1) a = a + b;执行完毕后,现在最新的a的值变成原先的a + b的值;
( 2 ) (2) (2) b = a - b;执行完毕后,相当于b的值变成了a + b - b,即原先a的值;
( 3 ) (3) (3) a = a - b;执行完毕后,相当于a的值变成了a + b - a,即原先b的值;
从而实现了变量a和b的交换。
3、正确解法3:引入异或运算
首先,介绍一下C语言中的^符号,代表的是异或。
二进制的异或,就是两个数转换成二进制表示后,按照位进行以下运算:
左操作数
右操作数
异或结果
0
0
0
1
1
0
0
1
1
1
0
1
也就是对于 0 和 1,相同的数异或为 0,不同的数异或为 1。
这样就有了三个比较清晰的性质:
1)两个相同的十进制数异或的结果一定位零。
2)任何一个数和 0 的异或结果一定是它本身。
3)异或运算满足结合律和交换律。
代码语言:javascript
复制
#include <stdio.h>
int main() {
int a, b;
while (scanf("%d %d", &a, &b) != EOF) {
a = a ^ b; // (1)
b = a ^ b; // (2)
a = a ^ b; // (3)
printf("%d %d\n", a, b);
}
return 0;
}
#include <stdio.h>
int main() {
int a, b;
while (scanf("%d %d", &a, &b) != EOF) {
printf("%d %d\n", b, a);
}
return 0;
}
你学废了吗 🤣?
2、例题2:整数溢出
一、题目描述
先输入一个 t ( t ≤ 100 ) t (t \le 100) t(t≤100),然后输入 t t t 组数据。每组输入为 4 个正整数 a , b , c , d ( 0 ≤ a , b , c , d ≤ 2 62 ) a,b,c,d(0 \le a,b,c,d \le 2^{62}) a,b,c,d(0≤a,b,c,d≤262),输出 a + b + c + d a+b+c+d a+b+c+d 的值。
#include <stdio.h>
typedef unsigned long long ull; // (1)
const ull MAX = (((ull)1)<<62); // (2)
int main() {
int t;
ull a, b, c, d;
scanf("%d", &t);
while (t--) {
scanf("%llu %llu %llu %llu", &a, &b, &c, &d); // (3)
if (a == MAX && b == MAX && c == MAX && d == MAX) // (4)
printf("18446744073709551616\n"); // (5)
else
printf("%llu\n", a + b + c + d); // (6)
}
return 0;
}
( 1 ) (1) (1) 由于这题数据量较大,所有数据都需要用64位无符号整型。ull作为unsigned long long的别名;
如果子问题的数目为 O ( n t ) O(n^t) O(nt),每个子问题需要用到 O ( n e ) O(n^e) O(ne) 个子问题的结果,那么我们称它为 tD/eD 的问题,于是可以总结出四类常用的动态规划方程:(下面会把opt作为取最优值的函数(一般取 m i n min min 或 m a x max max ), w ( j , i ) w(j, i) w(j,i)为一个实函数,其它变量都可以在常数时间计算出来)。
1、1D/1D
d [ i ] = o p t ( d [ j ] + w ( j , i ) ∣ 0 < = i < j ) d[i] = opt( d[j] + w(j, i) | 0 <= i < j ) d[i]=opt(d[j]+w(j,i)∣0<=i<j)
状态转移如图四所示(黄色块代表 d [ i ] d[i] d[i],绿色块代表 d [ j ] d[j] d[j]):
这类状态转移方程一般出现在线性模型中。
2、2D/0D
d [ i ] [ j ] = o p t ( d [ i − 1 ] [ j ] + x i , d [ i ] [ j − 1 ] + y j , d [ i − 1 ] [ j − 1 ] + z i j ) d[i][j] = opt( d[i-1][j] + x_i, d[i][j-1] + y_j, d[i-1][j-1] + z_{ij} ) d[i][j]=opt(d[i−1][j]+xi,d[i][j−1]+yj,d[i−1][j−1]+zij)
d [ i ] [ j ] = w ( i , j ) + o p t ( d [ i ] [ k − 1 ] + d [ k ] [ j ] ) d[i][j] = w(i, j) + opt( d[i][k-1] + d[k][j] ) d[i][j]=w(i,j)+opt(d[i][k−1]+d[k][j])
区间模型常用方程,如图所示:
另外一种常用的 2D/1D 的方程为:
d [ i ] [ j ] = o p t ( d [ i − 1 ] [ k ] + w ( i , j , k ) ∣ k < j ) d[i][j] = opt( d[i-1][k] + w(i, j, k) | k < j ) d[i][j]=opt(d[i−1][k]+w(i,j,k)∣k<j)
d [ i ] [ j ] = o p t ( d [ i ′ ] [ j ′ ] + w ( i ′ , j ′ , i , j ) ∣ 0 < = i ′ < i , 0 < = j ′ < j ) d[i][j] = opt( d[i’][j’] + w(i’, j’, i, j) | 0 <= i’ < i, 0 <= j’ < j) d[i][j]=opt(d[i′][j′]+w(i′,j′,i,j)∣0<=i′<i,0<=j′<j)
如图所示:
常见于二维的迷宫问题,由于复杂度比较大,所以一般配合数据结构优化,如线段树、树状数组等。
对于一个tD/eD 的动态规划问题,在不经过任何优化的情况下,可以粗略得到一个时间复杂度是 O ( n t + e ) O(n^ {t+e}) O(nt+e),空间复杂度是 O ( n t ) O(n^t) O(nt) 的算法,大多数情况下空间复杂度是很容易优化的,难点在于时间复杂度,后续章节将详细讲解各种情况下的动态规划优化算法。