位运算
理论
- ^1翻转 ^0不变
- &0置0 &1不变
- |1 置1 |0 不变
- 判断奇偶:n&1
- 将最右边的一个1置0:n&(n-1)
- 返回i的二进制最低位位1的权值:i & (-i)
- 正负数转换: ~a + 1
例子:
// k位置0:
x &= ~(1 << m)
// k位置1:
x |= (1 << m)
// k位取反:
x ^= (1 << m)
应用:
判断2的整次幂
if (n & (n - 1)) printf("NO");
计算一个数的二进制中1的个数:
//法1:
count = 0
while (a)
{
a = a & (a - 1);
count++;
}
//法2:见下面的「返回i的二进制最低位位1的权值」
count = 0
while (a)
{
a -= a & (-a);
count++;
}
//法3:
count = 0
while (a)
{
count += a & 1;
a >>>= 1;//无符号右移 相当于 /2
}
计算一个数的二进制中1的个数:
flag = 1;
while (flag)
{
if (num & flag)
n++;
flag = flag << 1;
}
异或
理论
- 交换律:a ^ b=b ^ a
- 结合律:(a ^ b) ^ c == a^ (b ^ c)
- 对于任何数x,都有x ^ x = 0,x ^ 0 = x
- 自反性: a ^ b ^ b = a ^ 0 = a;
应用
交换二个数:
a = a ^ b;
b = b ^ a;
a = a ^ b;
数组中,只有唯一的一个元素值重复,其它均只出现一次。
数组中,只有一个元素出现了奇数次,其它出现偶数次。
给出 n 个整数,n 为奇数,其中有且仅有一个数出现了奇数次,其余的数都出现了偶数次。
【奇巧淫技 - 位运算】
快速幂:
int pow(int n, int m)
{
int sum = 1;
int tmp = m;
while (n != 0)
{
if (n & 1 == 1)
{
sum *= tmp;
}
tmp *= tmp;
n = n >> 1;
}
return sum;
}
加法:
① 各位相加但不计进位
② 记下所被略去的进位
③ 将①与②相加
int Add(int num1, int num2)
{
int sum, carry;
while (num2 != 0)
{
//sum是和,而carry是进位,sum表示计算的①,carry表示计算的②,不断地进行①与②,直到②为0
sum = num1 ^ num2;
carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
}
return num1;
}
减法:
int Negtive(int num1, int num2)
{
//减法即num1+(-num2),注意计算机用补码存的,所以-num2 = ~(num2 - 1) = ~num2 + 1 = Add(~num2,1)
return Add(num1, Add(~num2, 1));
}
枚举:
#include <iostream>
#include <set>
#define MAXN 100
using namespace std;
set<int> s;
int main()
{
int n, a[MAXN], i;
cin >> n;
for (i = 0; i < n; i++)
{
cin >> a[i];
}
for (int x = 0; x < (1 << n); x++) //x范围:0到(2^n)
{
s.clear();//s储存要输出每次的数据
for (int i = 0; i < n; i++)
{
if (((x >> i) & 1)) // x的第i为如果是1,则a数组的第i个放入s中
s.insert(a[i]);
}
for (auto it : s)//输出s
{
cout << it << " ";
}
cout << endl;
}
}
参考:
【技巧总结】位运算装逼指南
C++ 算法篇 位运算
位运算——强大得令人害怕
【C++】位运算实现加减乘除
位运算技巧总结
贪心算法
一、基本概念:
贪心算法 | 在对问题求解时,总是做出在当前看来是最好的选择。 不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。 |
后效性 | 某个状态以后的过程不会影响以前的状态,只与当前状态有关。 |
性质 | 贪心算法以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。 对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。 |
注意 | 贪心策略一定要仔细分析其是否满足无后效性。 贪心算法前提:局部最优策略能导致产生全局最优解。 |
二、基本思路:
1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。
三、实现框架
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;
四、一般流程:
Greedy(C) //C是问题的输入集合即候选集合
{
S = { }; //初始解集合为空集
while (not solution(S)) //集合S没有构成问题的一个解
{
x = select(C); //在候选集合C中做贪心选择
if feasible(S, x) //判断集合S中加入x后的解是否可行
{
S = S + {x};
}
C = C - {x};
}
return S;
}
最小生成树的Prim算法
Kruskal算法
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
int sum = 0;
int m, n, i, j;
int str[100] = { 0 };
char first, finish;
cin >> m >> n;
vector<vector<int>> a(n, vector<int>(3));
//输入数据,把字母A-G转化为0-6
//储存在a中,a是n*3的二维vector,每行是距离+起点+终点
for (i = 0; i < n; i++)
{
cin >> first >> finish >> a[i][0];
a[i][1] = first - 'A';
a[i][2] = finish - 'A';
}
//排序
sort(a.begin(), a.end());
//从头到尾输出,输出前检查两个点是否都已经输出过,是,则跳过这条路
str[a[0][1]] = str[a[0][2]] = 1;
cout <<(char) (a[0][1] + 'A') << " - " << (char) (a[0][2] + 'A') << " : " << a[0][0] << endl;
sum += a[0][0];
for (i = 1; i < n; i++)
{
if (!str[a[i][1]]++ || !str[a[i][2]]++)
{
printf("%c - %c : %d\n", a[i][1] + 65, a[i][2] + 65, a[i][0]);
sum += a[i][0];
}
}
//sum是总路径
cout << "Total: " << sum;
}