位运算&贪心

位运算

理论

  • ^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;
}

参考:

贪心算法及几个经典例子
五大常用算法——贪心算法详解及经典例子

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值