1. 位运算
应用
- 一些有限状态集合可以通过位运算来快速表示,如状态压缩DP。
- 进行乘方和乘法的快速计算,如快速幂,慢速乘。
代码模板
最短Hamilton路径:从起点到终点,每个点只经过一次的最短路径
memset(dp, 0x3f, sizeof dp);dp[1][0] = 0;for(int i = 1; i < 1 << n; i++) {for(int j = 0; j < n; j++) {if(i >> j & 1) {for(int k = 0; k < n; k++) {if(k != j && i >> k & 1) {dp[i][j] = min(dp[i][j], dp[i & ~(1 << j)][k] + d[k][j]);}}}}}printf("%d\n", dp[(1 << n) - 1][n - 1]);
快速幂、慢速乘(大数乘法)
int qmi(int a, int b, int p){int ans = 1;for(; b > 0; b >>= 1) {if(b & 1) ans = (ll) ans * a % p;a = (ll) a * a % p;}return ans % p;}ll qadd(ll a, ll b, ll p){ll ans = 0;for(; b > 0; b >>= 1) {if(b & 1) ans = ((__int128)ans + a) % p;a = ((__int128)a + a) % p;}return ans;}
2. 排序
快排模板
void qsort(int l, int r) {if(l >= r) {return;}int i = l - 1, j = r + 1, x = a[l + r >> 1];while(i < j) {do i++; while(a[i] < x);do j--; while(x < a[j]);if(i < j) {swap(a[i], a[j]);}}qsort(l, j), qsort(j + 1, r);}
3. 贪心
贪心的题目没有特定的框架,需要具体问题具体分析,尽量分类清楚。
目前有这几种类型:
- 根据特定规则排序类:给你一个数组,一个规则,回答怎么对该数组排序,使得结果最优。这类问题通常是先假定结果已经按最优情况排好了,如果交换两个相邻的项(在不影响其他前后元素的基础上),结果会变差,然后推出排序规则。
- 有多个限制条件,然后不断地维护一个最优决策。这类题目通常有多个限制条件,我们需要先在满足一个条件地基础上,尽可能满足第二个条件,使得结果最优。解法通常是先按照条件一排序,然后贪心地选择条件二对结果更有利的项。一般需要仔细想想按照哪个条件排序,可以更方便维护当前的最优决策。
经典场景:
- 超市
有N个物品,每个物品都有一个价值和一个过期时间。假如每天只能卖一个物品,如何安排使得超市的收益最大。这道题可以先按照过期时间排序,先卖快过期的,然后对于每个物品,我们可以看当前合法决策下的最优物品集合,它的过期时间是不是比集合总数大,是的话可以直接作为候选项(可以安排最后卖这个物品),否则需要和集合中的物品的价值进行比较,如果比集合中价值最小的物品要大,那么把集合中价值最小的去掉,把这个物品加进入。
这道题还有另一种解法,按价值排序,优先放价值最大,然后对于新物品,查看有没有空闲日期放。维护空闲日期通过并查集来解决,使用掉一个日期,将该日期的父节点指向父节点的前一个结点。这样,对于任意一个过期时间,它的根都对应最近可选的空闲时间。下面是解法一的代码。
#include<cstdio>#include<queue>#include<algorithm>using namespace std;typedef pair<int,int> pii;const int N = 1e4 + 10;pii a[N];int main() {int n;priority_queue<int, vector<int>, greater<int>> pq;while(scanf("%d", &n) == 1) {for(int i = 0; i < n; i++) {scanf("%d%d", &a[i].second, &a[i].first);}sort(a, a + n);for(int i = 0; i < n; i++) {int d = a[i].first, v = a[i].second;while(pq.size() >= d && (int)pq.top() < v) {pq.pop();}if(d > (int)pq.size()) {pq.push(v);}}int ans = 0;while(pq.size()) {ans += pq.top();pq.pop();}printf("%d\n", ans);}return 0;}
- 耍杂技的牛(相似题:国王游戏)
有N头牛垂直站,每个牛都有一个重量和强壮值,每个牛的风险是在它之上所有牛的重量减去它自己的强壮值的结果,求这N头牛如何排列,使得每头牛最大的风险值最小。这道题是第一种类型的贪心题,我们发现在一种排序里,交换两头牛不会对其他牛的风险值产生影响,因此可以比较交换相邻两头牛,比较前后的最大值变化,推导出排序的规则。
#include<cstdio>#include<algorithm>using namespace std;const int N = 5e5 + 10;int a[N], b[N], c[N];bool cmp(int i, int j) {return a[i] + b[i] < a[j] + b[j];}int main() {int n;scanf("%d", &n);for(int i = 0; i < n; i++) {scanf("%d%d", &a[i], &b[i]);c[i] = i;}sort(c, c + n, cmp);int ans = -1e9, sum = 0;for(int i = 0; i < n; i++) {ans = max(ans, sum - b[c[i]]);sum += a[c[i]];}printf("%d\n", ans);return 0;}
