进阶数据结构与算法模板

1. STL

1.1 priority_queue

class Task {
public:
    string name;
    int priority;
    Task(string n, int p) : name(move(n)), priority(p) {}
    // 重载操作符'>'
    bool operator>(const Task& other) const {// 注意两处const
        return priority > other.priority;
    }
    bool operator<(const Task& other) const {
        return priority < other.priority;
    }
};

int main() {
    // 初始化优先队列(最小堆)
    priority_queue<Task, vector<Task>, greater<Task>> taskQueue;
    // 最大堆可以采取默认初始化:priority_queue<Task> maxHeap;
    // push操作
    taskQueue.push(Task("TaskA", 3));
    taskQueue.push(Task("TaskB", 1));
    taskQueue.push(Task("TaskC", 2));
    // 判断非空
    while (!taskQueue.empty()) {
        Task currentTask = taskQueue.top();
        cout << "Executing task: " << currentTask.name << " (Priority: " << currentTask.priority << ")\n";
        taskQueue.pop();
    }
}

1.2 multiset

// mutiset支持log n增删改查,而priority_queue不支持改和查
// 因此multiset爆杀优先队列,仅仅略多占用空间
mutiset<array<int, 5>> seq;
// array存储一系列节点信息,按照[0],[1],[2],[3],[4]依次排序

auto it = next(seq.begin(), n-1); // 访问第n个元素
auto pos = seq.lower_bound(target); // 获取第一个不小于target元素的迭代器
auto pre = prev(pos); // 从边界点向上寻找
if (pos != seq.end())
{
	int a = (*pos) + 1;
	seq.erase(pos); // 改动后需要删除,再插入
	seq.insert(a);
}

1.3 unordered

// 键值唯一,实现常数级的增删改查
// getline(cin, text);
string text = "This is a sample text with some sample words. "
"This text is used for demonstration purposes.";
unordered_set<string> uniqueWords;
unordered_map<string, int> wordCount;

istringstream iss(text);
string word;
// 利用字符流,以空格为分隔读入string作为单词
while (iss >> word) {
    for (char& c : word) {
        c = tolower(c);
    }
    // 进行统计,更新数据
    uniqueWords.insert(word);
    wordCount[word]++;
}
// 输出每个单次出现的单词
cout << "Number of unique words: " << uniqueWords.size() << endl;
// 输出每个单词和它的出现次数
for (const auto& pair : wordCount) {
    std::cout << pair.first << ": " << pair.second << " times\n";
}

// 求交集、并集、差集
set<int> a = {1,2,3,4,5}, b = {1,3,6}, c;
set_union(a.begin(), a.end(), b.begin(), b.end(), inserter(c, c.begin()));
set_difference();
set_intersection();//语法相同

1.4 stringstream

// istringstream仅读取字符流,stringstream在其基础上可以修改字符串
stringsteam ss;
int num = 42;
double pi = 3.1415926;
string words = "Combination of different objects";
ss << num << "," << pi << "," << words;
cout << ss.str() << endl;

1.5 algorithm


// 1. 找特定/最大/最小值
vector<int> number = {1, 2, 3, 4, 5};
int base[5] = {1, 2, 3, 4, 5};
cout << *max_element(number.begin(), number.end()) << ",";
cout << *min_element(base, base+5) << endl;
auto temp = find(arr.begin(), arr.end(), 3);
   cout << "value: " << *temp << "index: " << temp - arr.begin() << endl;
// 更方便的写法:必须提交初始化列表作为参数
min({1, 2, 3, 4, 5});

// 2. 排序和去重
function<bool(int, int)> cmp = [&](int a, int b)->bool{
	return a > b;
};
sort(number.begin(), number.end(), cmp);
auto uni = unique(number.begin(), number.end());

// 3. 数组操作
int books[1024];
fill(books, books + 1024, INT_MAX);
int numss[5] = {1,2,3,4,5};
reverse(numss, numss+5);
string aLine = "12345678";
reverse(aLine.begin(), aLine.end());
return 0;

1.6 numeric

vector<int> nums = {1,2,3,4,5,6,7};
// 1. 计算求和
cout << accumulate(nums.begin(), nums.end(), 0) << endl;
// 第三个参数是初始值,一般为0

// 2. 计算前缀和,时间复杂度O(n)
vector<int> partialSums(nums.size());
partial_sum(nums.begin(), nums.end(), partialSums);

// 3. 赋值数组
vector<int> arr(n+1);
iota(arr.begin(), arr.end(), 0);

vector<int> v1 = {1, 2, 3};
vector<int> v2 = {4, 5, 6};
// 4. 计算内积
int result = inner_product(v1.begin(), v1.end(), v2.begin(), 0);
cout << "Inner Product: " << result << endl;

// 5. gcd&lcd
int m = 12, n = 18;
cout << gcd(m,n) << "," << lcd(m,n) << endl;

1.7 bitset

// 1. 初始化
bitset<8> bs1;		// 默认全0
bitset<8> bs2(42);	// 值为42的8位二进制数
bitset<8> bs3("10101010")

// 2. 位操作
bool flag = bs2[2];
bs3.set(3);			// 设置第4位为1(0号位是第1位)
bs1.flip();			// 翻转所有位的值

// 3. 位运算
bitset<8> res1 = bs1 & bs2;	// 按位与
auto res2 = bs1 | bs2;		// 按位或
auto res3 = bs2 ^ bs3;		// 按位异或

1.7 C++特性

// 本地调试与优化输入输出
#define LOCAL
void work()
{
	string acp;
	cin >> acp;
	cout << acp << " -checked" << endl;
}
int main(){
	#ifdef LOCAL
		freopen("data-in.txt", "r", stdin);
		freopen("data-out.txt", "w", stdout);
	#endif
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int CASES = 5;
	while(CASES--) work();
	return 0;
}

// 建议编译器内联展开,优化函数效率
inline void func()
{
	return;
}

// remove_if-erase组合
nums.erase(remove_if(nums.begin(), nums.end(), 
	[](const auto &a){return a % 2 == 0;}), nums.end());
// 删除了数组中满足特定要求的元素,而不会导致迭代器紊乱
// 适用于deque、list等,而对于map等关联容器和stack、queue
// 只能通过for循环删除:
map<string, int> m = {{"a", 1}, {"b", 2}, {"c", 3}};
for (auto it = m.begin(); it != m.end(); it++)
{
	if (it[0] == 'a')
		it = m.erase(it);// erase返回下一个有效迭代器
}

// 判断字符串是否为整数
bool isInteger(const string& str) {
    try {
        stoi(str);  // 尝试将字符串转换为整数
        return true;     // 转换成功,字符串是整数
    } catch (const invalid_argument& e) {
        // 转换失败,字符串不是整数
        return false;
    } catch (const out_of_range& e) {
        // 数字超出了int的范围,但仍然是整数
        return true;
    }
}


// 类初始化
class person{
public:
    string name;
    int id;
    vector<string> titles;
    person(string name, int id) : name(name), id(id), titles(100, ""){};
};
// C++的结构体视作全部成员为public的类
struct person{
    string name;
    int id;
    vector<string> titles;
    person(string name, int id) : name(name), id(id), titles(100, ""){};
};
// 重构操作符
struct mat{
	long long a11, a12, a21, a22;
	// 如果定义了构造函数,就不可以再使用列表初始化
	mat operator* (const mat&a, const mat& b){
		long long c11, c12, c21, c22;
		// 模拟矩阵乘法
		return {c11, c12, c21, c22};
	}
	// 注意此处必须有friend关键字,作用是访问该类的私有属性
	friend istream& operator >> (istream& in, mat& x){
		return in >> x.a11 >> x.a12 >> x.a21 >> x.a22;
	}
	// 注意此处对象必须有const属性
	friend ostream& operator << (ostream& out, const mat& x){
		return out << x.a11;
	}
}

2. 进阶数据结构

2.1 并查集

力扣684. 冗余连接
https://leetcode.cn/problems/redundant-connection/
力扣685. 冗余连接 II
https://leetcode.cn/problems/redundant-connection-ii/

int find(vector<int>& parent, int index)
{
    if (index != parent[index])
        parent[index] = find(parent, parent[index]);
    return parent[index];
}
void Union(vector<int> &parent, int node1, int node2)
{
    // 2的父节点是1
    parent[find(parent, node2)] = find(parent, node1);
}
// 判断新加入的边是否构成了环:
if (find(parent, u) == find(parent, v))
	return {u,v}; // u和v点已经访问过(同根节点),因此再次访问则构成环
else
	Union(parent, u, v);// 将含v的子树并入u的根节点
// 注意由于Union前已经调用过find(parent,u),此时parent[u] == find(parent,u)
// 所以写哪个都对

2.2 字典树/前缀树(Trie)

力扣3045. 统计前后缀下标对 II
https://leetcode.cn/problems/count-prefix-and-suffix-pairs-ii/

struct node{
	unordered_map<int, node*> sons;
	int cnt;
};

node root = new node();
for (string s : words)
{
	auto cur = root;
	for (int i = 0; i < s.length(); i++)
	{
		int idx = (int) s[i] - 'a';
		// 一种同时统计前后缀的写法:
		// int idx = (int) s[i] - 'a' << 5 | s[n-i-1] - 'a';
		// 先去前缀的字母编号,再左移,腾出空间给后缀字母编号
		if (cur->sons[idx] == nullptr)
		{ // 添加新前缀
			cur->sons[idx] = new node();
		}
		cur = cur->sons[idx];
		ans += cur->cnt; // 计算当前具有该前缀的单词个数
	}
	cur->cnt++; // 以整个单词作为前缀,亦即提交给你前缀和目标单词的区分
	// 如果是要在所有目标单词中统计可能的前缀,则要在最深层循环中cnt++
}

2.3 差分数组

力扣1094. 拼车
https://leetcode.cn/problems/car-pooling/

// d[i] = a[i]-a[i-1] (a[0] = 0, d[1] = a[1])
// s[i+1] = s[i] + d[i], 则有s[i] = a[i]
// 对于需要对整个区间范围(如a[l]~a[r])同时增加一个数的时候
// 只需要对差分数组的两端进行:d[l] += v, d[r+1] -= v;
// 时间复杂度由O(n)到O(2)

// map实现(不需要重构的情况下省空间):
map<int, int> diff; // 注意不能用unordered_map,要保留顺序性
for (auto it : changes)
{
	 int from = it[0], to = it[1], v = it[2];
	 diff[from] += diff;
	 diff[to] -= diff;
}

// 数组实现
int diff[1024], len = 1024;
auto update = [&diff](int from, int to, int v){
	diff[from] += v;
	if (to+1 < len)
		diff[to+1] -= v;
};

// 重构结果
for (int i = 1; i < len; i++)
	diff[i] += diff[i-1];

2.4 二维前缀和

力扣1277. 统计全为 1 的正方形子矩阵
https://leetcode.cn/problems/count-square-submatrices-with-all-ones/

int m = matric.size(), n = matric[0].size();
vector<vector<int>> sum(m+1, vector<int>(n+1,0));
for (int i = 0; i < m; i++)
{
	for (int j = 0; j < n; j++)
	{
		sum[i+1][j+1]=sum[i+1][j] + sum[i][j+1] - sum[i][j] + metric[i][j];
		// 往往搭配ans使用,可以ans可以metric中全体位置的总和
		// 也可以是某个过程值
		ans += sum[i+1][j+1]
	}
}

2.5 树状数组

力扣307. 区域和检索 - 数组可修改
https://leetcode.cn/problems/range-sum-query-mutable/description/
力扣3072. 将元素分配到两个数组中 II
https://leetcode.cn/problems/distribute-elements-into-two-arrays-ii/

// 树状数组:更新/查询时间复杂度O(logN)
class treeArr{
private:
	vector<int> nums;
	vector<int> tree;// tree.size() = num.size()+1, 空间复杂度O(n)
	int prefixSum(int i){
		int s = 0;
		for (; i > 0; i &= i-1)// i -= i & -i
			s += tree[i];
		return s;
	}
public:
	treeArr(vector<int> &nums) : nums(nums.size()), tree(nums.size() + 1){
		for (int i =  0; i < nums.size(); i++)
		{
			update(i, nums[i]);
		}
	}
	int update(int index, int val)
	{
		int d = nums[index] - val; // 初始化时默认元素值为0
		nums[index] = val;
		for (int i = index; i < nums.size(); i += i & -i){
			tree[i] += d;
		}		
	}
	int getSumRange(int left, int right){// right left存放的是nums数组中元素的下标
		return prefixSum(right+1) - prefixSum(left);
		// prefixSum给出的是前i个元素的和,假设需要0~n-1的和,则为前n个数的和
		// 因此前right+1个数的和减去前left个数,是[left, right]区间和
	}
}

2.6 线段树

力扣2286. 以组为单位订音乐会的门票
https://leetcode.cn/problems/booking-concert-tickets-in-groups/

class SegTree{
public:
	int n; // 原数组长度
	vector<long long> sum;
	vector<int> minVal; // 要维护的区间特征值,这里以区间和、最小值为例
	// 用长度4n的数组模拟树结构,变量t是用来访问树节点的
	void add(int t, int l, int r, int idx, int val)
	{// 增加idx位置的值,l和r是左右端点,在二分过程中反复改变
		if (l==r) {
			minVal[t] += val;// 注意更新逻辑
			sum[t] += val;	 // 只用val直接修改最底层的叶子节点
			return;
		}
		int m = (l+r)/2;
		if (idx <= m) add(t*2, l, m, idx, val);
		else add(t*2+1, m+1, r, idx, val);
		// 以左右子树更新当前节点
		minVal[t] = min(minVal[2*t], minVal[2*t + 1]);
		sumVal[t] = sumVal[2*t] + sumVal[2*t + 1];
	}
	long long getSum(int t, int l, int r, int L, int R)
	{// 返回[L, R]区间和,关联于特征值sum
		if (L <= l && r <= R) return sum[t];
		// 当前区间在求和范围内,立刻返回累加
		long long res = 0;
		int m = (l+r)/2;
		// 所求区间不规则,用二分去逼近,最终分块得到了[L,R]
		if (L <= m)
			res += getSum(t*2, l, m, L, R);
		if (R > m)
			res += getSum(t*2+1, m+1, r, L, R);
		return res;
	}
	int index(int t, int l, int r, int R, int val)
	{// 查询[1,R]区间内<=val的位置,关联于特征值minVal
		if (minVal[t]> val)// 从根节点来看,都不符合条件
			return 0;
		if (l == r)// 找到了
			return l;
		int m = (l+r)/2;
		if (minVal[t*2] <= val)// 左子树满足条件,规定先去左子树寻找
			return index(t*2, l, m, R, val);
		else if (m< R)// 左子树不满足再去右子树,要求m+1<=R
			return index(t*2+1, m+1, r, R, val);
		return 0; // 保证出口
	}
}

2.7 网络流

力扣2850. 将石头分散到网格图的最少移动次数
https://leetcode.cn/problems/minimum-moves-to-spread-stones-over-grid/

2.8 分块数组

// 分块数组好处在于连接区间时更加直观
struct node{
	// 各种数据
	int op;
	mat l, r;// 模拟矩阵乘法,无交换律区分左右
}arr[N];
// n为数组总长度
int LEN = max(1, (int)sqrt(n));
struct block{
	int front, back, sz;
	long long suml[M], sumr[M];
	// idx是块编号
	void build(int idx)
	{
		front = idx*LEN, back = min(front + LEN - 1, n - 1);
		sz = back - front + 1;
		for (int i = 0; i < sz; i++)
		{
			int c = i + front;
			suml[i+1] = arr[c].l * suml[i];
			sumr[i+1] = sumr[i].r*arr[c];
		}
}blk[M];

for (int i = 0; i < n; i += LEN)
	blk[i/LEN].build(i/LEN);

3. 图论

3.1 Dijkstra&Floyd(最短路)

Dijkstra:单源最短路径
力扣1976. 到达目的地的方案数
https://leetcode.cn/problems/number-of-ways-to-arrive-at-destination/

// 贪心算法:每次都选取最短的边进行构造
vector<vector<pair <int, int>>> edges;// <距离,终点>
vector<long long> dis(n,0);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<>> pq;
while(true)
{
	auto [dx, x] = pq.top();
	// dx:迄今为止路径总长
	pq.pop();
	if (x == n-1)
		break;// 算法结束,已找到0->n-1的最短路
	if (dx > dis[x])
		continue;
	for (auto &[dy, y] : edges[x])
	{
		int new_dis=dy+dis[x];
		if (new_dis < dis[y])
		{
			dis[y] = new_dis;
			pq.push(new_dis, y); // 加入可能最短的0->y路径
			// 如果不是最短,已经在if(dx>dis[x])排除了
		}
	}
}

Floyd:全源最短路径
力扣1334. 阈值距离内邻居最少的城市
https://leetcode.cn/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/description/

// 查找所有两点间最短路径
// 关键点:dp(k+1, i, j) = max( dp(k, i, j), dp(k, i, k) + dp(k, k, j) )
// 把除了ij以外的所有点视作中间节点,用选与不选缩小问题范围
// k从n-1开始遍历,意为前k个节点,即考虑了所有编号<=k的节点
vector<vector<int>> edges(n, vector<int>(n, INT_MAX/2));
// 回溯+记忆化
int memo[128][128][128] = {0};
auto dfs = [&memo] (int k, int i, int j){
	if (k<0) // 遍历到k=-1,算法结束
		return edges[i][j];
	return memo[k][i][j] = min(dfs(k-1, i, j), dfs(k-1, i, k) + dfs(k-1, k, j));
}
// 动态规划O(n^3)空间
vector<vector<vector<int>>> dp(n, vector<int>(n, vector<int> (n) ) );
dp[0] = edges;// 当k=0,意为无中间节点,则距离等于单边
for (int k = 0; k < n; k++)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0;j < n; j++)
		{
			dp[k+1][i][j] = min(dp[k][i][j], dp[k][i][k]+dp[k][k][j];
			// 对于dp[k][i][k],可知i到k的中间节点肯定没有k
			// 所以推导出dp[k][i][k] = dp[k-1][i][k]
			// 则对于所有的z,dp[z][i][z] = dp[z-1][i][z-1],dp[k][k][j]同理
			// 说明位置前后不改变,可以放心变成二维
		}
	}
}

// 动态规划O(n^2)空间
vector<vector<int>> dp = edges;
for (int k = 0; k < n; k++)
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0;j < n; j++)
		{
			dp[i][j] = min(dp[i][j], dp[i][k]+dp[k][j];
		}
	}
}

3.2 Kruskal&Prim(MST,Minimum Spanning Tree)

力扣1584. 连接所有点的最小费用
https://leetcode.cn/problems/min-cost-to-connect-all-points/

3.3 SPFA(Shortest Path Faster Algorithm)

3.4 二分图

4. 树上

4.1 树链剖分

设nd.sz表示以某个节点为根的子树的节点个数
对于每个非叶子节点,sz值最大的子节点就是重儿子,其他为轻儿子
重儿子及父节点连起来就是重链
树链剖分就是把重链放在一起做成线性区间,按该顺序以线段树维护
在这里插入图片描述
顺序为1,3,6,10,8,9,7,2,5,4

首先,使用O(n)的dfs(dfs1),求出depth(深度),father,size,heavy_son(重儿子)。

然后,再使用O(n)的dfs(dfs2),求出top、to_tree、to_num
对于点x,若x为重儿子,top为x所在重链的顶端;若x为轻儿子,top=x(可以将每个轻儿子当成一条重链)。
求top其实很简单,递归时设一个参数k,表示x.top。
递归给重儿子时,k不变;
递归给轻儿子时,k为它本身。
对于点x,to_tree表示x在线段树中的位置。对于线段树中的第i个位置,to_num为它对应树中的位置。

const int N = 100010, M = N << 1;
int n, m, rt, mod;
int w[N], h[N], e[M], ne[M], idx;
int id[N], nw[N], cnt;
int dep[N], sz[N], top[N], fa[N], son[N];
struct segtree
{
	int l, r;
	ll add, sum;
}tr[N << 3];
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
	return;
}
void dfs1(int p, int father, int depth)
{
	dep[p] = depth, fa[p] = father, sz[p] = 1;
	for(int i = h[p]; ~i; i = ne[i])
	{
		int j = e[i];
		if(j == father) continue;
		dfs1(j, p, depth + 1);
		sz[p] += sz[j];
		if(sz[son[p]] < sz[j]) son[p] = j;
	}
	return;
}
void dfs2(int p, int t)
{
	id[p] = ++cnt, nw[cnt] = w[p], top[p] = t;
	if(!son[p]) return;
	dfs2(son[p], t);
	for(int i = h[p]; ~i; i = ne[i])
	{
		int j = e[i];
		if(j == fa[p] || j == son[p]) continue;
		dfs2(j, j);
	}
	return;
}
void pushup(int p)
{
	tr[p].sum = (tr[p << 1].sum + tr[p << 1 | 1].sum) % mod;
	return;
}
void pushdown(int p)
{
	auto &root = tr[p], &left = tr[p << 1], &rght = tr[p << 1 | 1];
	if(root.add)
	{
		left.add = (left.add + root.add) % mod;
		left.sum = (left.sum + root.add * (left.r - left.l + 1)) % mod;
		rght.add = (rght.add + root.add) % mod;
		rght.sum = (rght.sum + root.add * (rght.r - rght.l + 1)) % mod;
		root.add = 0;
	}
	return;
}
void build(int p, int l, int r)
{
	tr[p] = { l, r, 0, nw[r] };
	if(l == r) return;
	int mid = (l + r) >> 1;
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
	pushup(p);
	return;
}
void segadd(int p, int l, int r, int k)
{
	if((l <= tr[p].l) && (r >= tr[p].r))
	{
		tr[p].add = (tr[p].add + k) % mod;
		tr[p].sum = (tr[p].sum + k * (tr[p].r - tr[p].l + 1)) % mod;
		return;
	}
	pushdown(p);
	int mid = (tr[p].l + tr[p].r) >> 1;
	if(l <= mid) segadd(p << 1, l, r, k);
	if(r > mid) segadd(p << 1 | 1, l, r, k);
	pushup(p);
	return;
}
ll segsum(int p, int l, int r)
{
	if((l <= tr[p].l) && (r >= tr[p].r)) return tr[p].sum;
	pushdown(p);
	int mid = (tr[p].l + tr[p].r) >> 1;
	ll res = 0;
	if(l <= mid) res += segsum(p << 1, l, r);
	if(r > mid) res += segsum(p << 1 | 1, l, r);
	return res % mod;
}

4.2 树上DP

5. 算法模板

5.1 字符串

5.1.1 扩展KMP(Z函数)

力扣3036. 匹配模式数组的子数组数目 II
https://leetcode.cn/problems/number-of-subarrays-that-match-a-pattern-ii/

5.1.2 字符串哈希

力扣1392. 最长快乐前缀
https://leetcode.cn/problems/longest-happy-prefix/

5.1.3 AC自动机

5.2 DP

5.2.1 状态压缩DP

5.2.2 数位DP

5.2.3 划分DP

6. 数学

#include <cmath>
sin(), cos(), tan();

double intPart, fracPart, toBeSplit = 3.125;
fracPart = modf(toBeSplit, &intPart);

// 筛法判断素数
vector <int> pms;
bool isPrime[N];
fill(isPrime + 1, isPrime + N + 1, true);
void isPm()
{
    for (int i = 2; i*i <=n; i++)
    {
        if (isPrime[i])
        {
            pms.push_back(i);
            for (int j = i*i; j <=n; j+=i)
                isPrime[j] = false;
        }
    }
}
// 分解质因子
typedef vector<pair<int, int>> vpii;
vpii divide(int n)
{
    vpii res;
    for (int i= 2; i*i <= n; i++)
    {
        if (n%i==0)
        {
            int j = 0;
            while(n%i==0)
            {
                n/=i;
                j++;
            }
            res.push_back({i,j});
        }
    }
}

6.1 位运算技巧

// 按位 &与, |或, ^亦或, ~取反
int n, x, k;
x << n;         // 左移n位
x >> n;         // 右移n位
                // 逻辑右移
x|1;            // 最后一位变为1
(x|1)-1;        // 最后一位变为0
x^1;            // 最后一位取反
 
x|(1<<(k-1))    // 右数第k位变1
x& ~(1<<(k-1))  // 右数第k位变0
x^(1<<(k-1))    // 右数第k位取反
x&(1<<k -1)     // 取末k位数字
x>>(k-1) &1     // 取右数第k位数字
 
x&(x+1)         // 把右起连续的1变0, 前面的不变, 如010111->010000
x|(x+1)         // 把右起第一个0变1
 
void swap(int &a, int &b)
{
    a ^= b;
    b ^= a;
    a ^= b;
}
int change(int a)
{
    return ~a +1;
}
// 统计二进制1个数
int count(int x){
    int cnt = 0;
    while(x){
        cnt += x & 1;
        x = x >> 1;
    }
    return cnt;
}

6.2 概率论

6.3 线性代数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值