voidquick_sort(int q[], int l ,int r) { if (l >= r) return; int i = l - 1, j = r + 1, x = q[l + r >> 1]; while (i < j){ do i++ ; while (q[i] < x); do j-- ; while (q[j] > x); if (i < j ) swap (q[i], q[j]); } quick_sort(q, l, j); quick_sort(q, j+1, r); }
intmain() { scanf("%d",&n); for (int i = 0; i < n; i++) scanf("%d", &q[i]); quick_sort(q, 0, n - 1); for (int i = 0; i < n; i++) printf("%d", q[i]); return0; }
usingnamespace std; constint N = 1e6 + 10; int n; int p[N], temp[N];
voidmerge_sort(int p[], int l, int r){ if (l >= r) return; // 先划分 int mid = l + r >> 1; merge_sort(p, l, mid); merge_sort(p, mid + 1, r); //合并 int i = l, j = mid + 1, k = 0; while(i <= mid && j <= r){ if (p[i] <= p[j]) temp[k++] = p[i++]; else temp[k++] = p[j++]; } //扫尾 while (i <= mid) temp[k++] = p[i++]; while (j <= r) temp[k++] = p[j++]; //还给p for(int i = l, j = 0 ; i<= r; i++, j++) p[i] = temp[j]; }
intmain(){ scanf("%d",&n); for (int i = 0 ; i < n; i++) scanf("%d", &p[i]); merge_sort(p, 0, n - 1); for (int i = 0; i < n; i++) printf("%d ", p[i]); }
intmain(){ scanf("%d%d", &n, &q); for (int i = 0; i < n; i++) scanf("%d", &a[i]); while (q--){ int num; scanf("%d", &num); int l = 0, r = n - 1; while (l < r){ int mid = l + r >> 1; if (num <= a[mid]) r = mid; // [l, mid]和[mid + 1, r] else l = mid + 1; } if (a[l] != num) printf("-1 "); elseprintf("%d ",l); l = 0, r = n - 1; while (l < r){ int mid = l + r + 1 >> 1; if (num >= a[mid]) l = mid; // [l, mid - 1]和[mid, r] else r = mid - 1; } if (a[l] != num) printf("-1\n"); elseprintf("%d\n",l); } return0; }
4.浮点数二分
不用怎么考虑边界问题,小到一定的位数1e-6就可以把边界当做答案
精度问题可以自己调整: 如果题目保留4位小数,精度写1e-6; 5位,1e-7;至少要多2
1 2 3 4 5 6 7 8 9 10 11 12 13
boolcheck(double x){/* ... */} // 检查x是否满足某种性质
doublebsearch_3(double l, double r) { constdouble eps = 1e-6; // eps 表示精度,取决于题目对精度的要求 while (r - l > eps) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; } return l; }
// C = A + B, A >= 0, B >= 0 vector<int> add(vector<int> &A, vector<int> &B) { if (A.size() < B.size()) returnadd(B, A);
vector<int> C; int t = 0; // 进位 for (int i = 0; i < A.size(); i ++ ) { t += A[i]; if (i < B.size()) t += B[i]; C.push_back(t % 10); t /= 10; } // 别忘了第一位 可能进位 if (t) C.push_back(t); return C; }
// C = A - B, 满足A >= B, A >= 0, B >= 0 vector<int> sub(vector<int> &A, vector<int> &B) { vector<int> C; for (int i = 0, t = 0; i < A.size(); i ++ ) { t = A[i] - t; if (i < B.size()) t -= B[i]; C.push_back((t + 10) % 10); if (t < 0) t = 1; else t = 0; } // 去掉前置 0 while (C.size() > 1 && C.back() == 0) C.pop_back(); return C; }
// 判断A>=B boolcmp(vector<int>& A,vector<int>& B){ if (A.size() > B.size()) returntrue; elseif (A.size() < B.size()) returnfalse; for (int i = A.size() -1; i >= 0; i--){ if (A[i] != B [i]) return A[i] - B[i] > 0; } returntrue; }
5.3 高精度乘以低精度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// C = A * b, A >= 0, b >= 0 vector<int> mul(vector<int> &A, int b) { vector<int> C;
int t = 0; // 要么A没算完,要么t不等于0 for (int i = 0; i < A.size() || t; i ++ ) { if (i < A.size()) t += A[i] * b; C.push_back(t % 10); t /= 10; }
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C; }
5.4 高精度除以低精度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// A / b = C ... r, A >= 0, b > 0 vector<int> div(vector<int> &A, int b, int &r) { vector<int> C; r = 0; for (int i = A.size() - 1; i >= 0; i -- ) { r = r * 10 + A[i]; C.push_back(r / b); r %= b; } reverse(C.begin(), C.end()); while (C.size() > 1 && C.back() == 0) C.pop_back(); return C; }
// 二分求出x对应的离散化的值 int find(int x) // 找到第一个大于等于x的位置 { int l = 0, r = alls.size() - 1; while (l < r) { int mid = l + r >> 1; if (alls[mid] >= x) r = mid; else l = mid + 1; } return r + 1; // 映射到1, 2, ...n 之后前缀和好处理边界0 }
unique()函数底层实现原理
1 2 3 4 5 6 7 8
vector<int>::iterator unique(vector<int> &a){ int j = 0; for (int i = 0; i < a.size(); ++i) { if (!i || a[i] != a[i - 1])//如果是第一个元素或者该元素不等于前一个元素,即不重复元素,我们就把它存到数组前j个元素中 a[j++] = a[i];//每存在一个不同元素,j++ } return a.begin() + j;//返回的是前j个不重复元素的下标 }
int st = -2e9, ed = -2e9; // 维护一个当前的区间,左端点一定是最小的 for (auto seg : segs) if (ed < seg.first) { // 分散的区间,一种情况 if (st != -2e9) res.push_back({st, ed}); st = seg.first, ed = seg.second; } else ed = max(ed, seg.second); // 包含两种情况
// 从后往前计算表达式 voideval() { auto b = num.top(); num.pop(); auto a = num.top(); num.pop(); auto c = op.top(); op.pop(); int x; if (c == '+') x = a + b; elseif (c == '-') x = a - b; elseif (c == '*') x = a * b; else x = a / b; num.push(x); }
intmain() { unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; string str; cin >> str; for (int i = 0; i < str.size(); i++) { auto c = str[i]; if (isdigit(c)) { int x = 0, j = i; while (j < str.size() && isdigit(str[j])) x = x * 10 + str[j ++] - '0'; i = j - 1; // 别忘了更新i num.push(x); } elseif (c == '(') op.push(c); elseif (c == ')') { while (op.top() != '(') eval(); op.pop(); } else { // 计算时为空 或者 上一步的运算符比这一步等级高 就能进行计算 while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval(); op.push(c); } } while (op.size()) eval(); cout << num.top() << endl; return0; }
5. 单调栈
老是忘记写法,多记几次
左边第一个小的数 i从0开始 维护一个递增的栈
右边第一个小的数 i从 n-1开始 维护一个递增的栈
原理:
在左边或者右边维护一个栈, 栈是单调递增或者递减的
想法:
先想暴力的,发现有些没有用的数可以删掉,然后发现单调性
维护单调性,如果求极值就维护左右两端点,如果是求其中值就用二分
1 2 3 4 5 6 7
常见模型:找出每个数左边离它最近的比它大/小的数 int tt = 0; for (int i = 1; i <= n; i ++ ) { while (tt && check(stk[tt], i)) tt -- ; // 维护单调栈 stk[ ++ tt] = i; // 别忘了插入当前的数 }
6. 单调队列
常见模型:找出滑动窗口中的最大值/最小值
单调队列解决滑动窗口,不仅处理尾部来保证单调性,而且要处理窗口滑动导致的极值消失问题
1 2 3 4 5 6 7
int hh = 0, tt = -1; for (int i = 0; i < n; i ++ ) { while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口 while (hh <= tt && check(q[tt], i)) tt -- ; q[ ++ tt] = i; }
voidinsert(char str[]) { // 当前节点 int p = 0; for (int i = 0; str[i]; i++) { int u = str[i] - 'a'; // 往下找子节点,如果不存在就添加子节点 if (!son[p][u]) son[p][u] = ++ idx; p = son[p][u]; } cnt[p] ++; }
intquery(char str[]) { int p = 0; for (int i = 0; str[i]; i++) { int u = str[i] - 'a'; // 某一结点没找到,返回 if (!son[p][u]) return0; p = son[p][u]; } return cnt[p]; }
// 交换两个点,及其映射关系 voidheap_swap(int a, int b) { // 这里a b是堆位置输入的,所以hp[a]才有意义,ph[a]没什么意义 // 这里把两个数组都swap一下,对应映射交换 swap(ph[hp[a]],ph[hp[b]]); swap(hp[a], hp[b]); swap(h[a], h[b]); }
// 和左节点和右节点更小的点交换,递归down voiddown(int u) { int t = u; if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2; if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1; if (u != t) { heap_swap(u, t); down(t); } }
// 和父节点交换 voidup(int u) { while (u / 2 && h[u] < h[u / 2]) { heap_swap(u, u / 2); u >>= 1; } }
voidheap_swap(int a, int b) { // 这里a b是堆位置输入的,所以hp[a]才有意义,ph[a]没什么意义 // 这里把两个数组都swap一下,对应映射交换 swap(ph[hp[a]], ph[hp[b]]); swap(hp[a], hp[b]); swap(h[a], h[b]); }
voiddown(int u) { int t = u; if (2 * u <= cnt && h[t] > h[2 * u]) t = 2 * u; if (2 * u + 1 <= cnt && h[t] > h[2 * u + 1]) t = 2 * u + 1; if (u != t) { heap_swap(u, t); down(t); } }
voidup(int u) { while (u/2 && h[u/2] > h[u]) { heap_swap(u/2, u); u >>= 1; } }
queue<int> q; for (int i = 1; i <= n; i ++ ) { q.push(i); st[i] = true; }
while (q.size()) { auto t = q.front(); q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) returntrue; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环 if (!st[j]) { q.push(j); st[j] = true; } } } }
returnfalse; }
6) floyd算法
O(N^3) 求解多源最短路问题
一般是多次多点查询,查任意d[a][b]
1 2 3 4 5 6 7 8 9 10 11 12 13 14
初始化: for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) if (i == j) d[i][j] = 0; else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离 voidfloyd() { for (int k = 1; k <= n; k ++ ) for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); }
int n; // n表示点数 int h[N], e[M], ne[M], idx; // 邻接表存储图 int color[N]; // 表示每个点的颜色,0表示未染色,1表示白色,2表示黑色
// 参数:u表示当前节点,c表示当前点的颜色 // 如果下一个点 已经被染色 并且 和上一个同色,false booldfs(int u, int c) { color[u] = c; for (int i = h[u]; i != -1; i = ne[i]) { int j = e[i]; if (!color[j]) { if (!dfs(j, 3 - c)) returnfalse; } elseif (color[j] == c) returnfalse; }
returntrue; } // 染色法 boolcheck() { bool flag = true; // 遍历所有点 for (int i = 1; i <= n; i ++ ) if (!color[i]) // 从这个点出发 染色相连的点 if (!dfs(i, 1)) { flag = false; break; } return flag; }
7. 二分图的最大匹配 匈牙利算法
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数 int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边 int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个 bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过
boolfind(int x) { for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true; if (match[j] == 0 || find(match[j])) // 没有匹配男生,或者那个男的可以找到下家 { match[j] = x; returntrue; } } }
returnfalse; }
// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点 int res = 0; for (int i = 1; i <= n1; i ++ ) { memset(st, false, sizeof st); if (find(i)) res ++ ; }
数学知识
1. 质数
试除法 判断质数
O(sqrt(n))
原理:i能整除n,那么n/i也能整除n,都是成对出现的,那只需要枚举小的那个数
只需要枚举到根号n就行
1 2 3 4 5 6 7 8
boolis_prime(int x) { if (x < 2) returnfalse; for (int i = 2; i <= x / i; i ++ ) // 不要写成根号n,比较慢;也不要写i*i < n可能存在int移除 if (x % i == 0) returnfalse; returntrue; }
试除 分解质因数法
O(sqrt(n))
原理:n中最多只包含一个大于sqr(n)的质因子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
voiddivide(int x) { for (int i = 2; i <= x / i; i ++ ) // 从小到大分解质因子,修改x // i 一定是质数 : 因为如果i能被2到i-1的数整除,那么n一定能被2到i-1中的数整除,因为n是i的倍数(n能被i整除) 因为在枚举到i之前已经把n中2到i-1的质因子除干净了,此时n中不含2到i-1的质因子,由于n为i的倍数,所以i中也不包含2到i-1的质因子 if (x % i == 0) { int s = 0; while (x % i == 0) x /= i, s ++ ; cout << i << ' ' << s << endl; } if (x > 1) cout << x << ' ' << 1 << endl; // 最多只有一个大于根号n的质因子 cout << endl; }
朴素筛法 求范围内所有素数
O(Nlog(N)) 不管是素数还是合数,都筛掉他的倍数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int primes[N], cnt; // primes[]存储所有素数 bool st[N]; // st[x]存储x是否被筛掉
voidget_primes(int n) { for (int i = 2; i <= n; i ++ ) { // 把素数存起来 if (!st[i]) primes[cnt ++ ] = i; // [2, p-1]都没有被筛掉,所以是素数 // 不管是素数还是合数,都筛掉他的倍数 for (int j = i + i; j <= n; j += i) st[j] = true; } }
诶氏筛法 求范围内所有素数
O(Nlog(log(N)) 只用素数,都筛掉所有的合数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
int primes[N], cnt; // primes[]存储所有素数 bool st[N]; // st[x]存储x是否被筛掉
voidget_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i]) { // 把素数存起来 primes[cnt ++ ] = i; // [2, p-1]都没有被筛掉,所以是素数 // 只用素数,都筛掉所有的合数 for (int j = i + i; j <= n; j += i) // 把每个数的倍数删掉 : i 是一个质数, i的倍数都筛掉 st[j] = true; } } }
int primes[N], cnt; // primes[]存储所有素数 bool st[N]; // st[x]存储x是否被筛掉
voidget_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ) { st[primes[j] * i] = true; // 用最小质因子去筛合数 //1)当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,那么只可能是此时的primes[j]一定是小于i的最小质因子,所以primes[j]*i的最小质因子就是primes[j]; //2)当有i%primes[j]==0时,说明i的最小质因子是primes[j],因此primes[j]*i的最小质因子也就应该是prime[j], // 之后接着用st[primes[j+1]*i]=true去筛合数时,就不是用最小质因子去更新了。因为i有最小质因子primes[j]<primes[j+1],此时的primes[j+1]不是primes[j+1]*i的最小质因子, // 此时应该退出循环,增加i,然后把primer[j]重新从小开始 if (i % primes[j] == 0) break; } } }
2. 约数
和求质因数一样,约数也是成对出现,遍历到根号n,只遍历较小的约数就行,顺便把不同的另一个约数也插入
试除法求所有约数
1 2 3 4 5 6 7 8 9 10 11 12
vector<int> get_divisors(int x) { vector<int> res; for (int i = 1; i <= x / i; i ++ ) if (x % i == 0) { res.push_back(i); if (i != x / i) res.push_back(x / i); } sort(res.begin(), res.end()); return res; }
usingnamespace std; typedeflonglong LL; constint mod = 1e9 + 7;
intmain() { int n; cin >> n; unordered_map <int, int> primes; // 每个数约数及其次数,总乘数的约束及其次数 while ( n--) { int x; cin >> x; for (int i = 2; i <= x / i; i++) while (x % i == 0) x /= i, primes[i] ++; if (x > 1) primes[x] ++; } // 公式 LL res = 1; for (auto prime : primes) { int p = prime.first, a = prime.second; LL t = 1; while (a--) t = (t*p + 1) % mod; res = res * t % mod; } cout << res << endl; return0; }
辗转相除法 求最大公约数
欧几里得算法
1 2 3 4
intgcd(int a, int b) { return b ? gcd(b, a % b) : a; }
原理:()表示最大公约数,
首先(a, b) = (a, ka + b), 那么可以用辗转相减法求(a,b) :基本上思路就是大数减去小数,一直减到能算出来为止
intphi(int x) { int res = x; for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) { res = res / i * (i - 1); while (x % i == 0) x /= i; } if (x > 1) res = res / x * (x - 1);
int primes[N], cnt; // primes[]存储所有素数 int euler[N]; // 存储每个数的欧拉函数 bool st[N]; // st[x]存储x是否被筛掉
voidget_eulers(int n) { euler[1] = 1; for (int i = 2; i <= n; i ++ ) { if (!st[i]) { primes[cnt ++ ] = i; // 质数的欧拉函数 i-1 euler[i] = i - 1; } for (int j = 0; primes[j] <= n / i; j ++ ) { int t = primes[j] * i; st[t] = true; // primes[j]是i的最小质因子, 也是rimes[j] * i的最小质因子,因此1 - 1 / primes[j]这一项在phi[i]中计算过了,只需将基数NN修正为primes[j]倍,最终结果为phi[i] * primes[j] if (i % primes[j] == 0) { euler[t] = euler[i] * primes[j]; break; } // primes[j]不是i的质因子,只是primes[j] * i的最小质因子,因此不仅需要将基数NN修正为primes[j]倍,还需要补上1 - 1 / primes[j]这一项,因此最终结果phi[i] * (primes[j] - 1) euler[t] = euler[i] * (primes[j] - 1); } } }
4. 快速幂
O(n∗logb)
求 m^k mod p,时间复杂度 O(logk)。
把m^k 转化为 m^(2^0 + 2^1 + 2^2 +…),
如何预处理 m^(2^b) mod p这些值, 每个数就是前面的数 平方 再mod p
1 2 3 4 5 6 7 8 9 10 11 12 13 14
求 m^k mod p,时间复杂度 O(logk)。
intqmi(int m, int k, int p) { int res = 1 % p; while (k) { if (k&1) res = res * m % p; // 下一个底数和次数 m^(2^b) , 下一个 k a = a * a % p; k >>= 1; } return res; }
快速幂求逆元
逆元:a / b ≡ a * x (mod n),则x是b的逆元。 数b与逆元x的关系 1 ≡ b * x (mod n)
费马小定理:当n为质数时b ^ (n - 1) ≡ 1 (mod n)
拆一个b出来可得 b * b ^ (n - 2) ≡ 1 (mod n) 故当n为质数时,b n 互质,b的乘法逆元 b的乘法逆元 = b ^ (n - 2) mod n
LL qmi(int a, int b, int c) { LL res = 1 % c; while (b) { if (b & 1) res = res * a % c; b >>= 1; a = (LL)a * a % c; } return res; }
intmain() { int n; scanf("%d", &n); while (n -- ) { int a, p; scanf("%d%d", &a, &p); // 题目中限定了 p 一定是质数,所以如果ab不互质,即 a 和 p 有公因子,那么 a 就一定是 p 的倍数 if (a % p == 0) puts("impossible"); elseprintf("%lld\n", qmi(a, p-2, p)); } return0; }
5.扩展欧几里得算法
裴蜀定理:
有一对任意正整数a, b, 那么一定存在整数系数x, y, 使得ax + by = (a, b) 。()表示最大公约数
而且这个(a,b)是a和b能凑出来的最小的数
证明:
因为a b能凑出来的数一定是(a,b)的倍数,所以最小的数是(a,b)
求出系数 x y
扩展欧几里得算法求 系数x, y
1 2 3 4 5 6 7 8 9 10 11 12
// 求x, y,使得ax + by = gcd(a, b) intexgcd(int a, int b, int &x, int &y) { if (!b) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= (a/b) * x; return d; }
intexgcd(int a, int b, int& x, int& y) { if (!b) { x= 1, y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= a / b *x; return d; }
intmain() { int n; scanf("%d", &n); while (n -- ) { int a, b, m; scanf("%d%d%d", &a, &b, &m); int x, y; int d = exgcd(a, m, x, y); // b 是(a, m)的倍数 才有解 if (b % d) puts("impossible"); // 结果需要扩大 b/d 倍 elseprintf("%lld\n", (longlong) x * (b/d) % m); } return0; }
// a[N][N]是增广矩阵 intgauss() { int c, r; // 按照每列遍历 for (c = 0, r = 0; c < n; c ++) { // 找到该列最大值的行 int t = r; for (int i = r; i < n; i++) if (fabs(a[i][c]) > fabs(a[t][c])) t = i; // 如果最大是0,已经完成变换 if (fabs(a[t][c]) < eps) continue; // 把绝对值最大的行换到最顶端 for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]); // 把当前行的首位变成1 for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; // 用当前行将下面所有列消0 for (int i = r + 1; i < n; i++) if (fabs(a[i][c]) > eps) for (int j = n; j >= c; j-- ) a[i][j] -= a[r][j] * a[i][c]; r ++; } if (r < n) { for (int i = r; i < n; i ++) // 如果存在0 = 非0 无解 if (fabs(a[i][n]) > eps) return2; // 无穷多解 return1; } // 唯一解 for (int i = n - 1; i >= 0; i --) for (int j = i + 1; j < n; j++) // 消掉除了对角线的项,最后化成 xi = 常数项 a[i][n] -= a[i][j] * a[j][n]; return0; }
7. 组合数
递推法求组合数
组合数的递推式:
1 2 3 4 5 6 7 8 9 10 11
// c[a][b] 表示从a个苹果中选b个的方案数 for (int i = 0; i < N; i ++ ) for (int j = 0; j <= i; j ++ ) if (!j) c[i][j] = 1; else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; /* 考虑边界情况: 1.c[0][j] = 0 2.c[i][0] = 1 3. i < j 时,0 不存在 4. i = j 是,1 */
首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N] 如果取模的数是质数,可以用费马小定理求逆元 intqmi(int a, int k, int p)// 快速幂模板 { int res = 1; while (k) { if (k & 1) res = (LL)res * a % p; a = (LL)a * a % p; k >>= 1; } return res; }
// 预处理阶乘的余数和阶乘逆元的余数 fact[0] = infact[0] = 1; for (int i = 1; i < N; i ++ ) { fact[i] = (LL)fact[i - 1] * i % mod; infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod; }
usingnamespace std; constint N = 110, M = 10010; int n, m; int s[N], f[M];
// 求出每一堆的sg intsg(int x) { if (f[x] != -1) return f[x]; unordered_set<int> S; // 每个节点的往下子节点的值存在set中 for (int i = 0; i < m; i++) { // 要用递归的话,最好定义局部变量,防止改变原值 int sum = s[i]; if (x >= sum) S.insert(sg(x - sum)); // 延伸到终点的sg值后,再从后往前排查出所有数的sg值 } //最小的没有出现的自然数的操作 for(int i = 0; ;i ++) if (!S.count(i)) return f[x] = i; }
intmain() { cin >> m; for (int i = 0; i < m; i ++ ) cin >> s[i]; cin >> n;
memset(f, -1, sizeof f); int res = 0; for (int i = 0; i < n; i ++ ) { int x; cin >> x; res ^= sg(x); // 所有堆的sg异或起来 } if (res) puts("Yes"); elseputs("No"); return0; }
constint N = 1010; int n, m; int v[N], w[N]; int f[N][N];
intmain() { cin >> n >> m; for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i]; for (int i = 1; i <= n; i++) for (int j = 0; j <= m; j++) for (int k = 0; k * v[i] <= j; k++) f[i][j] = max (f[i][j], f[i -1][j - v[i] * k] + w[i] * k); cout << f[n][m] << endl; return0; }
constint N = 1010; int n, m; int v[N], w[N], s[N]; int f[N][N];
intmain() { cin >> n >> m; for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i]; for (int i = 1; i <= n; i++) for (int j = 0; j <= m; j++) for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ ). f[i][j] = max (f[i][j], f[i -1][j - v[i] * k] + w[i] * k); cout << f[n][m] << endl; return0; }
constint N = 12000, M = 2010; // N = 1000 * (log(2000)/log(2)) int n, m; int v[N], w[N]; int f[M];
intmain() { cin >> n >> m; int cnt = 0; for (int i = 1; i <= n; i++) { int a, b, s; cin >> a >> b >> s; // 二进制优化,从1开始分(1,2,4,8.16.....比s小的2倍数,c(余数)) int k = 1; while (k <= s) { cnt ++; v[cnt] = a * k; w[cnt] = b * k; s -= k; k *= 2; } if (s > 0) { cnt ++; v[cnt] = a * s; w[cnt] = b * s; } } n = cnt; for (int i = 1; i <= n; i++) for (int j = m; j >= v[i]; j--) f[j]= max (f[j], f[j - v[i]] + w[i]); cout << f[m] << endl; return0; }
#include<iostream> usingnamespace std; constint N = 310; int a[N][N]; int len = 0; int r, c; int dir[4][2] = {{0, -1}, {0, 1}, {1, 0}, {-1, 0}};
intdfs(int x, int y) { int num = 1; int res = 1; for (int i = 0; i < 4; i++) { int nx = x + dir[i][0], ny = y + dir[i][1]; if (nx > 0 && nx <= r && ny > 0 && ny <= c && a[nx][ny] < a[x][y]) { int t = dfs(nx, ny); num += t ; res = max(res, num); num -= t; } } return res; }
intmain() { cin >> r >> c; for (int i = 1; i <= r; i++) for (int j = 1; j <= c; j++) cin >> a[i][j];
for (int i = 1; i <= r; i++) for (int j = 1; j <= c; j++) len = max(len, dfs(i, j)); cout << len << endl; return0; }
intdp(int x, int y) { int &v = f[x][y]; if (v != -1) return v; v = 1; for (int i = 0; i < 4; i++) { int nx = x + dir[i][0], ny = y + dir[i][1]; if (nx > 0 && nx <= r && ny > 0 && ny <= c && a[nx][ny] < a[x][y]) { int t = dp(nx, ny); v = max(v, t + 1); } } return v; }
intmain() { cin >> r >> c; for (int i = 1; i <= r; i++) for (int j = 1; j <= c; j++) cin >> a[i][j]; memset (f, -1, sizeof f); for (int i = 1; i <= r; i++) for (int j = 1; j <= c; j++) len = max(len, dp(i, j)); cout << len << endl; return0; }
贪心
没有固定的模板,按步骤贪心,选择当前最好的情况,举一些例子试试贪心想法对不对,证明比较难
区间问题
区间选点
给定 N 个闭区间 [ai,bi][ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
usingnamespace std; constint N = 100010; int n; structRange { int l, r; booloperator < (const Range &w) const { return r < w.r; } } range[N];
intmain() { scanf("%d", &n); for (int i = 0; i < n; i++) { int l, r; scanf("%d%d", &l, &r); range[i] = {l, r}; } sort(range, range + n); // 先定一个无限小的ed,这种把第一个区间res+1的技巧 int res = 0, ed = -2e9; for (int i = 0; i < n; i++) { if (range[i].l > ed) { res ++; ed = range[i].r; } } printf("%d", res); return0 ; }
constint N = 100010; int n; structRange { int l, r; booloperator< (const Range& w) const { return r < w.r; } }range[N];
intmain() { scanf("%d", &n); for (int i = 0; i < n; i++) { int l, r; scanf("%d%d", &l, &r); range[i] = {l, r}; } sort(range, range + n); int res = 0, ed = -2e9; for (int i = 0; i < n; i++) { if (ed < range[i].l) { res ++; ed = range[i].r; } } printf("%d\n", res); return0; }
区间分组
给定 N 个闭区间 [ai,bi][ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小
structRange { int l, r; booloperator< (const Range &w) { return l < w.l; } }range[N];
int n;
intmain() { int st, ed; scanf("%d%d", &st, &ed); scanf("%d", &n); for (int i = 0; i < n; i++) { int l, r; scanf("%d%d", &l, &r); range[i] = {l, r}; } sort (range, range + n); int res = 0; bool success = false; for (int i = 0; i < n; i ++) { int j = i, r = -2e9; while (j < n && range[j].l <= st) { r = max(r, range[j].r); j++; } res ++; // 找不到覆盖 if (r < st) { res = -1; break; } // 找完全覆盖 if (r >= ed) { success = true; break; } st = r; i = j - 1; } if (!success) res = -1; printf("%d", res); return0; }
intmain() { scanf("%d", &n); priority_queue<int, vector<int>, greater<int>> heap; while (n--) { int x; scanf("%d", &x); // 每次合并权值最小的两个点 #include<iostream> #include<algorithm> #include<queue>
usingnamespace std; int n;
intmain() { scanf("%d", &n); priority_queue<int, vector<int>, greater<int>> heap; while (n--) { int x; scanf("%d", &x); heap.push(x); }
int res = 0; while (heap.size() > 1) { int a = heap.top(); heap.pop(); int b = heap.top(); heap.pop(); res += b + a; heap.push(a + b); } printf("%d\n", res); return0; } }
int res = 0; while (heap.size() > 1) { int a = heap.top(); heap.pop(); int b = heap.top(); heap.pop(); res += b + a; heap.push(a + b); } printf("%d\n", res); return0; }
usingnamespace std; constint N = 100010; int n; int t[N];
intmain() { longlong res = 0; scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &t[i]); sort(t, t + n); for (int i = 0; i < n; i++) res += t[i] * (n - i - 1); printf("%lld", res); return0; }
intmain() { longlong res = 0; scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d", &a[i]); sort(a, a + n); for (int i = 0; i < n; ++i) res += abs(a[i] - a[n/2]); printf("%lld", res); return0; }
typedef pair<int , int> PII; int n; PII cow[N]; intmain() { scanf("%d", &n); for (int i = 0; i < n; i++) { int w, s; scanf("%d%d", &w, &s); cow[i] = {w + s, w}; } sort(cow, cow + n); int res = -2e9, sum = 0; for (int i = 0; i < n; i++) { int w = cow[i].second, s = cow[i].first - cow[i].second; res = max (res, sum - s); sum += w; } printf ("%d\n", res); return0; }