算法优化设计整理

  • 第二章——算法设计优化
    • 线性扫描、单调性问题:
      • 第k个数问题:
        • 1.POJ2388——nth_element()
      • 正数连续子段和:
        • 1.UVA1121——前缀和
      • 即时维护最大值:
        • 1.UVA11078——max()维护
      • Floyd 判圈:
        • 1.UVA11549——Floyd判圈、双指针
      • 中途相遇二分:
        • 1.UVA1152——equal_range()
      • 滑动窗口:
        • 1.UVA11572——set集合、滑动窗口
        • 2.POJ2823——单调队列(模拟双端队列)
      • 扫描线:
        • 1.UVA1398——扫描线
    • 排序问题:
      • 逆序对:
        • 1.POJ1804——逆序对
    • 动态规划:
      • 背包DP:
        • 01背包:
          • 1.POJ3624——01背包滚动数组优化
          • 2.ACwing423——01背包模板题
          • 3.ACwing1024——01背包应用
          • 4.ACwing278——01背包求方案数
        • 完全背包:
          • 1.ACwing1023——完全背包求方案数
          • 2.ACwing1021——完全背包求方案数
          • 3.ACwing532——极大独立集、完全背包求方案数应用
        • 多重背包:
          • 1.ACwing1019——多重背包朴素版
        • 分组背包:
          • 1.ACwing9——分组背包
        • 有依赖的背包:
          • 1.ACwing10——树形DP、分组背包
          • 2.ACwing1074——分组背包、树形DP
        • 二维背包:
          • 1.ACwing1022——二维背包、答案信息枚举
      • 区间DP:
        • 1.ACwing252——区间DP、前缀和、贪心
      • 树形DP:
        • 极大独立集:
          • 1.ACwing285没有上司的舞会——树形DP、求最大和
          • 2.ACwing323战略游戏——树形DP、求最小和
          • 3.ACwing1077皇宫看守——复杂状态的树形DP
        • 树的最长路径:
          • 1.ACwing1072——树的最长路径模板题
          • 2.ACwing1075——约束和NlogN处理,无根树最长路径
        • 树的中心:
          • 1.ACwing1073——上下深搜
  • 第四章——数据结构
    • 堆(Heap)
    • 平衡树
      • Treap
      • Splay
        • 1.ACwing253——Splay模板题前驱后继删除
        • 2.ACwing256——Splay简单应用
        • 3.ACwing1063——并查集、Splay合并
    • 分块
      • 1.ACwing243——分块求和
    • 分块——块状链表
  • 图论
    • 最大流
      • 最大流判定(二分框架)
        • 1.ACwing2277——最大流判定、二分
      • 分层图
        • 1.ACwing2187——分层图、顺序枚举最大流判定
      • 拆点
        • 1.ACwing2240——拆点
    • 有向图的强连通分量
      • 1.ACwing1174——tarjan、缩点法
    • 无向图的双连通分量
      • 边的双连通分量
        • 1.ACwing395——边的双连通分量、缩点
  • 数学
    • 扩展欧几里得定理
      • 1.ACwing877——扩展欧几里得定理
      • 2.ACwing878——线性同余方程
    • 欧拉函数
      • 1.ACwing873——欧拉函数

第二章——算法设计优化

线性扫描、单调性问题:

第k个数问题:

1.POJ2388——nth_element()

思路分析:nth_element 函数的使用。
nth_element 用法: nth_element(a, a+k, a+n);
nth_element 时间复杂度: O(N)。
nth_element 结果:将第 k 个元素放在整个数组中的第 k 个位置,并且左侧的元素都比这个数字小,但是不保证有序,右侧都比这个元素大,同样不保证有序。
nth_element详解

#include <iostream>
#include <algorithm>
#include <cassert>
using namespace std;
const int N = 1e6+7;
int a[N];
int main() {int n;scanf("%d", &n);for (int i = 0; i < n; i++) {scanf("%d", &a[i]);}nth_element(a, a+n/2, a+n);printf("%d\n", a[n/2]);return 0;
}

正数连续子段和:

1.UVA1121——前缀和

题目要求:找出最短的连续子段,这个子段大于等于 S。
(N < 10,0000,S < 1,0000,0000)。
思路:连续字段和,这种连续的和,正常的复杂度是平方级别,技巧就是前缀和预处理数组的和,达到降维的目的,之后使用双指针利用正数子段和递增的性质使用双指针维护子段区间。

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5 + 7;
int a[N], b[N];
int main() {int n, s;while (scanf("%d%d", &n, &s) != EOF) {for (int i = 1; i <= n; i++) scanf("%d", &a[i]);b[0] = 0;for (int i = 1; i <= n; i++) b[i] = b[i-1]+a[i];int ans = n+1, i = 1;for (int j = 1; j <= n; j++) {if (b[j] - b[i] < s) continue;while (i <= j) {if (b[j] - b[i] < s) break;i++;}ans = min(ans, j-i+1);}printf("%d\n", ans == n+1 ? 0: ans);}return 0;
}

处理连续子段和时,使用前缀和会帮到很大的忙!

即时维护最大值:

1.UVA11078——max()维护

正常枚举是平方复杂度,但是可以看出来的是,针对于每个数字,答案就是他前边最大的数字减去他的和,也就是说每次更新最大值即可,即可优化到线性。

/*
10.35  10.43
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5+7;
int a[N];
int main() {int T, n;scanf("%d", &T);while (T--) {scanf("%d", &n);for (int i = 0; i < n; i++) scanf("%d", &a[i]);int m = a[0], ans = a[0] - a[1];for (int i = 1; i < n; i++) {ans = max(ans, m-a[i]);m = max(m, a[i]);}printf("%d\n", ans);}return 0;
}

Floyd 判圈:

Floyd 判圈:使用两个指针在链表上滑动,快指针的速度是慢指针的两倍,如果最后相遇,则有圈,否则无圈。
圈的入口:固定一个指针,另外一个指针回到起点,以同样的速度滑动,最后会在起点相遇。
证明结论成立,即证 a == 整数倍环长+c。
设起点到环入口距离为 a,入口到相遇点的距离为 b,相遇点到入口的距离为 c,设相遇时快指针比慢指针夺多了 k 圈,慢指针走了 m 圈。
则有等式:a+b+m(b+c) = 1/2 * (a+b+(m+k)(b+c))
化简得: 2a+2b+2m(b+c) = a+b+(m+k)(b+c)
a+b=(k-m)(b+c) = K(b+c)
证明出:a+b 等于整数倍的环长 L,也就是说 a+L-c=K*L,所以 a=(K-1)L+c,因为 K-1是整数,所以结论成立!

1.UVA11549——Floyd判圈、双指针

思路分析:可以发现,计算器的每个输出是一个状态,必定会输出另一个结果,如果只要其中的前 n 位数字,那就有可能会出现重复,因为一共就只有 10n 个数字。水平有限,无法证明具体这个循环的量级是多少,但是至少题目给 6s,应该是不用考虑数据的问题,所以正常找环即可。
取前 n 位的写法很巧妙!!!!while (xx >= top) xx/=10;

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
ll k;
ll next(ll top, ll x) {ll xx = x*x;while (xx >= top) xx/=10;return xx;
}
int main() {int T, n;scanf("%d", &T);while(T--) {scanf("%d%lld", &n, &k);ll top = 1;for (int i = 0; i < n; i++) top = top*10;ll ans = k, x1 = k, x2 = k;while(1) {x1 = next(top, x1);x2 = next(top, x2);if (ans < x2) ans = x2;x2 = next(top, x2);if (ans < x2) ans = x2;// printf("%lld, %lld\n", x1, x2);if (x1 == x2) break;}printf("%lld\n", ans);}return 0;
}

中途相遇二分:

将一个步骤特别多的问题,从两边分别考虑,达到从中间判定是否满足题意的思想。

1.UVA1152——equal_range()

思路:在四个集合中找到四个数字,这四个数字是分别从四个集合中取得的,需要满足四个数字之和等于 0。正常来说,是 n3 的复杂度,但只要将两个集合一起考虑,后两个集合一起考虑,即可完成 n2 的复杂度优化。
详细过程:将前两个数组每对元素的和存入一个新的数组,并排序,之后使用二分扫描区间。
equal_range:equal_range 是 C++ STL 中的一种二分查找的算法,试图在已排序的[first,last)中寻找value,它返回一对迭代器 <i,j>,其中 i 是在不破坏次序的前提下,value 可插入的第一个位置(亦即lower_bound),j 则是在不破坏次序的前提下,value可插入的最后一个位置(亦即upper_bound),因此,[i, j) 内的每个元素都等同于 value,而且 [i, j) 是 [first, last) 之中符合此一性质的最大子区间。
equal_range详解

// 2021.11.11  9.08 9.33
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e4+7;
int a[N], b[N], c[N], d[N], sum[N*N];
int main() {int T, n;scanf("%d", &T);while(T--) {scanf("%d", &n);for (int i = 0; i < n; i++) scanf("%d%d%d%d", &a[i], &b[i], &c[i], &d[i]);int cc = 0;for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) sum[cc++] = a[i] + b[j];sort(sum, sum+cc);long long cnt = 0;for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {pair<int*, int*> p = equal_range(sum,sum+cc,-c[i]-d[j]);cnt += p.second - p.first;}}printf("%lld\n", cnt);if (T) printf("\n");}return 0;
}

将一个问题从前后一起考虑,头尾一起运算,中间一起在考虑,即可达到最优。

滑动窗口:

1.UVA11572——set集合、滑动窗口

滑动窗口:适用于维护连续的区间。
题目含义:找最大的区间,使得区间里每个数字都互不相同。
解决方案:相当于一个滑动窗口,只要有和右端元素相同的元素,比出现在最左端,因为时刻保证窗口中的数字都不相同,从一开始便维护这个滑动窗口。
基本操作:使用一种数据结构(队列+vis和set都可以)维护实时区间,只要需要扩充区间,只要不满足条件,就将最开始的元素删除,直至满足条件,再将新元素放置数据结构中。即可维护连续区间,在维护时实时更新最优解。

// 2021.11.11 9/58 10.12
#include <iostream>
#include <set>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e6+7;
int a[N];
int main() {int T, n;scanf("%d", &T);while(T--) {scanf("%d", &n);set<int> st;for (int i = 0; i < n; i++) scanf("%d", &a[i]);st.insert(a[0]);int l = 0, r = 1, ans = 0;while(r < n) {// if (l < r && st.count(a[r])) st.erase(a[r]);while (l < r && st.count(a[r])) st.erase(a[l++]);st.insert(a[r++]);ans = max(r-l, ans);}printf("%d\n", ans);}return 0;
}

2.POJ2823——单调队列(模拟双端队列)

单调队列:顾名思义,队列中所有元素单调递增,要么单调递减,是严格的!
编码技巧:保证每个元素必放进队列,这样只需要枚举队头是否区间长度小于 k 即可,之后以此队尾和欲插入元素进行比较,如果是单调递增序列,那么比当前元素大的元素都出队。
单调性判断:若是区间最小值,则可以想到,每次插入的元素一定影响着后续区间的最值,所以如果当前队列中有元素比它大,那他就没有存在的必要,因为,之前的区间由队头决定(因为单调,队头最小),而后续的最值由当前元素影响,所以他没有存在的必要,所以出队。区间最大值同理。

// 2021.11.11 10.20  10.52
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e6+7;
int q[N], a[N];
int main() {int n, k;scanf("%d%d", &n, &k);for (int i = 0; i < n; i++) scanf("%d", &a[i]);int h = 0, r = 0;for (int i = 0; i < n; i++) {while (h < r && q[h] + k <= i) h++;while (h < r && a[q[r-1]] >= a[i]) r--;q[r++] = i;if (i >= k - 1) printf("%d ", a[q[h]]);}puts("");h = 0, r = 0;for (int i = 0; i < n; i++) {while(h < r && q[h] + k <= i) h++;while(h < r && a[q[r-1]] <= a[i]) r--;q[r++] = i;if (i >= k - 1) printf("%d ", a[q[h]]);}puts("");return 0;
}

扫描线:

1.UVA1398——扫描线

将立体二维的点区间问题,通过时间的不等式关系,判断出两个维度的时间区间,取交集,L取大的,R取小的,这样掐两头单区间的即可得到星星在相机范围的时间范围,再分别将起始时间和结束时间存入数组,排序模拟即可。
这种有明显区间开始结束,并要求取最多数量的题目,要想到将区间转化为起始结束点,之后扫描线模拟过程。
坑点:开 2 倍数组!!!!
书中还有一个点拨的地方:若想使用 int,则可以使用 lcm(1,2,3,…,10)= 2520 来是所有时间整数化

// 11.13
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2e5+7;
int w, h, n;
struct node {double x;int type;bool operator < (const node &a) const {return x < a.x || (x == a.x && type > a.type);}
}nodes[N];
// 0 < x+at < w
// a>0: -x/a<t  a<0: -x/a>t
//      w-x/a>t      w-x/a<t
void update(int x, int a, int w, double &L, double &R) {// if (a == 0) {//     if (x <= 0 || x >= w) R = L-1;// }// else if (a > 0){//     L = max(L, -(double)x/a);//     R = min(R, (double)(w-x)/a);// }// else {//     L = max(L, (double)(w-x/a));//     R = min(R, -(double)x/a);// }if (a == 0) {if (x <= 0 || x >= w) R = L-1;}else if (a > 0) {L = max(L, -(double)x/a);R = min(R, (double)(w-x)/a);}else {L = max(L, (double)(w-x)/a);R = min(R, -(double)x/a);}
}
int main() {int T, x, y, a, b;scanf("%d", &T);while(T--) {int e = 0, ans = 0, cnt = 0;scanf("%d%d%d", &w, &h, &n);for (int i = 0; i < n; i++) {scanf("%d%d%d%d", &x, &y, &a, &b);double L = 0, R = 1e9;update(x,a,w,L,R);update(y,b,h,L,R);if (R >= L) {nodes[e++] = {L, 0};nodes[e++] = {R, 1};}}sort(nodes, nodes+e);for (int i = 0; i < e; i++) {if (nodes[i].type == 0) ans = max(ans, ++cnt);else cnt--;}printf("%d\n", ans);}   return 0;
}

排序问题:

逆序对:

1.POJ1804——逆序对

模板题:用归并排序的思想。

// 11.11  15:14   15:45
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <cassert>
using namespace std;
const int N = 1e6+7;
int a[N], b[N], n;
int merge(int l, int r) { // [)if (r - l <= 1) return 0;int mid = (l+r)/2, ans = merge(l, mid) + merge(mid, r);copy(a+l, a+r, b+l); // a copy bint i = l, j = mid, k = l;while (i < mid || j < r) {if (i < mid && b[i] <= b[j] || j >= r) a[k++] = b[i++];else ans += mid-i, a[k++] = b[j++];}return ans;
}
int main() {int T, cnt = 0;scanf("%d", &T);while(T--) {scanf("%d", &n);for (int i = 0; i < n; i++) scanf("%d", &a[i]);int ans = merge(0, n);printf("Scenario #%d:\n%d\n", ++cnt, ans);if (T) puts("");}return 0;
}

动态规划:

背包DP:

01背包:

1.POJ3624——01背包滚动数组优化
//11/11 18.31  18.42
#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 2e4+7;
int w[N], v[N], dp[N];
int main() {int n, m;scanf("%d%d", &n, &m);for (int i = 0; i < n; i++) scanf("%d%d", &w[i], &v[i]);for (int i = 0; i < n; i++) {for (int j = m; j >= w[i]; j--) {dp[j] = max(dp[j-w[i]]+v[i], dp[j]);}}printf("%d\n", dp[m]);return 0;
}
2.ACwing423——01背包模板题
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3+7;
int dp[N], w[N], v[N];
int main() {int n, m;scanf("%d%d", &m, &n);for (int i = 0; i < n; i++) scanf("%d%d", &w[i], &v[i]);for (int i = 0; i < n; i++) {for (int j = m; j >= w[i]; j--) {dp[j] = max(dp[j], dp[j - w[i]] + v[i]);}}printf("%d\n", dp[m]);return 0;
}
3.ACwing1024——01背包应用
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2e4+7;
ll dp[N], w[N];
int main() {int n, m;scanf("%d%d", &m, &n);for (int i = 0; i < n; i++) scanf("%lld", &w[i]);for (int i = 0; i < n; i++) {for (int j = m; j >= w[i]; j--) {dp[j] = max(dp[j], dp[j-w[i]]+w[i]);}}printf("%lld\n", m - dp[m]);return 0;
}
4.ACwing278——01背包求方案数

每次转移加上前一个状态的值,而不是方案数+1!!!

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5+7;
int dp[N];
int main() {int n, m, x;scanf("%d%d", &n, &m);dp[0] = 1;for (int i = 0; i < n; i++) {scanf("%d", &x);for (int j = m; j >= x; j--)  dp[j] = max(dp[j], dp[j-x]+dp[j]);}printf("%d\n", dp[m]);return 0;
}

完全背包:

1.ACwing1023——完全背包求方案数
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e3+7;
int dp[N], a[] = {10, 20, 50, 100}, n;
int main() {scanf("%d", &n);dp[0] = 1;for (int i = 0; i < 4; i++) {for (int j = a[i]; j <= n; j++) {dp[j] = max(dp[j], dp[j-a[i]]+dp[j]);}}printf("%d\n", dp[n]);return 0;
}
2.ACwing1021——完全背包求方案数

3000 不到的数据,之后 15 个面值的货币,就能超过 int 范围!!

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e3 + 7;
typedef long long ll;
ll dp[N], n, m;
int main() {scanf("%lld%lld", &n, &m);dp[0] = 1;for (int i = 0; i < n; i++) {ll x;scanf("%lld", &x);for (int j = x; j <= m; j++) dp[j] += dp[j-x];}printf("%lld\n", dp[m]);return 0;
}
3.ACwing532——极大独立集、完全背包求方案数应用

性质1:a 中的数字如果能被其他数字表示,则该去除。
性质2:b 中的数字不能相互表示。
性质3:本题需要明确三个性质,b 数组一定由 a 数组中的元素取得,因为如果 b = a1+a2,因为 a1 和 a2 可以用其他的 bi 来表示,这就说明 b 可以由别的 bi 表示,不满足性质2。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3e4+7;
int dp[N], a[N], n, m;
int main() {int T;scanf("%d", &T);while (T--) {scanf("%d", &n);for (int i = 0; i < n; i++) scanf("%d", &a[i]);sort(a, a+n);memset(dp, 0, sizeof dp);m = a[n-1];int ans = 0;dp[0] = 1;for (int i = 0; i < n; i++) {if (!dp[a[i]]) ans++;for (int j = a[i]; j <= m; j++) dp[j] += dp[j-a[i]];}printf("%d\n", ans);}return 0;
}

多重背包:

先书写一个错误代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 6e3+7;
int dp[N], n, m;
int main() {scanf("%d%d", &n, &m);for (int i = 0; i < n; i++) {int v, w, k;scanf("%d%d%d", &v, &w, &k);for (int num = 0; num <= k && num*v <= m; num++) {for (int j = m; j >= v; j--) {dp[j] = max(dp[j], dp[j-v] + w);}}}printf("%d\n", dp[m]);return 0;
}

如上代码,我的意图是将多重背包拆分成 k 次,更新 k 次就可以得到新的 dp 数组,但实际上,更新几次都是一样的,因为价值和消耗是一样的,更新不上去还是更新不上去,更新上去也不会再继续再原来数值的基础上再加上价值。所以仍然需要将多个物品捆绑在一起,这样才能加。

1.ACwing1019——多重背包朴素版
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 6e3+7;
int dp[N], n, m;
int main() {scanf("%d%d", &n, &m);for (int i = 0; i < n; i++) {int v, w, k;scanf("%d%d%d", &v, &w, &k);for (int j = m; j >= v; j--) {for (int l = 1; l <= k && l*v <= j; l++) dp[j] = max(dp[j], dp[j - l*v] + l*w);}}printf("%d\n", dp[m]);return 0;
}

分组背包:

1.ACwing9——分组背包

可化简成 1 维。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e2+7;
int dp[N][N], w[N][N], v[N][N], s[N], n, m;
int main() {scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++) {scanf("%d", &s[i]);for (int j = 0; j < s[i]; j++) {scanf("%d%d", &w[i][j], &v[i][j]);}}for (int i = 1; i <= n; i++) {for (int j = m; j >= 0; j--) {dp[i][j] = dp[i - 1][j];for (int k = 0; k < s[i]; k++) {if (w[i][k] <= j) dp[i][j] = max(dp[i][j], dp[i-1][j-w[i][k]] + v[i][k]);}}}printf("%d\n", dp[n][m]);return 0;
}

有依赖的背包:

1.ACwing10——树形DP、分组背包
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e2+7;
int dp[N][N], v[N], w[N], n, m, p, root;
int h[N], e[N], ne[N], cnt;
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
void dfs(int u) {for (int i = h[u]; ~i; i = ne[i]) {int j = e[i];dfs(j);for (int k = m-w[u]; k >= 0; k--) {for (int t = 0; t <= k; t++) {dp[u][k] = max(dp[u][k], dp[u][k - t] + dp[j][t]);}}}for (int j = m; j >= w[u]; j--) dp[u][j] = dp[u][j - w[u]] + v[u];for (int j = 0; j < w[u]; j++) dp[u][j] = 0;
}
int main() {memset(h, -1, sizeof h);scanf("%d%d", &n, &m);for (int i = 1; i <= n; i++) {scanf("%d%d%d", &w[i], &v[i], &p);if (p == -1) root = i;else add(p, i);}dfs(root);printf("%d\n", dp[root][m]);return 0;
}
2.ACwing1074——分组背包、树形DP
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 2e2+7;
int dp[N][N], n, m;
int h[N], e[N], ne[N], w[N], a[N], cnt;
void add(int u, int v, int k) {e[cnt] = v, w[cnt] = k, ne[cnt] = h[u], h[u] = cnt++;
}
void dfs1(int u, int pa) {for (int i = h[u]; ~i; i = ne[i]) {int v = e[i], val = w[i];if (v == pa) continue ;a[v] = val;dfs1(v, u);}
}
void dfs(int u,int pa) {for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (v == pa) continue;dfs(v, u);for (int j = m; j >= 0; j--) for (int k = 0; k < j; k++)dp[u][j] = max(dp[u][j], dp[u][j-k-1] + dp[v][k] + w[i]);// dp[u][1] += max(dp[v][0], dp[v][1]);// dp[u][0] += dp[v][0];}
}
int main() {scanf("%d%d", &n, &m);memset(h, -1, sizeof h);for (int i = 1; i <= n - 1; i++) {int x, y, v;scanf("%d%d%d", &x, &y, &v);add(x, y, v); add(y, x, v);}// dfs1(1, -1);dfs(1, -1);printf("%d\n", dp[1][m]);return 0;
}

二维背包:

1.ACwing1022——二维背包、答案信息枚举

题目分析:最大值对应的最小体力,可以通过枚举 dp 数组得到,因为 dp 数组是单调递增的,反着枚举,第一个不是最大值的就是答案。不过有两种创建的 dp 数组的方式,第二种更快一些。
memset() 是按字节赋值!!!
memset(dp, 0x3f, sizeof dp) 最后结果是 0x3f3f3f3f;
memset(dp, 2000, sizeof dp) 最后是 4 个 (2000 取前 8 位的数字)。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e3+7;
int dp[N][N], w[N], beat[N];
int main() {int n, m, k;scanf("%d%d%d", &k, &m, &n);for (int i = 0; i < n; i++) scanf("%d%d", &w[i], &beat[i]);for (int i = 0; i < n; i++) for (int j = k; j >= w[i]; j--) for (int l = m-1; l >= beat[i]; l--) dp[j][l] = max(dp[j][l], dp[j-w[i]][l-beat[i]] + 1);int ii = m-1;while (ii && dp[k][ii - 1] == dp[k][m - 1]) ii--;printf("%d %d\n", dp[k][m-1], m - ii);return 0;
}#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e3+7;
int dp[N][N], w[N], beat[N], n, m, k;
int main() {scanf("%d%d%d", &k, &m, &n);for (int i = 0; i < n; i++) scanf("%d%d", &w[i], &beat[i]);memset(dp, 0x3f, sizeof dp);// printf("%x\n", dp[2][3]);dp[0][0] = 0;for (int i = 0; i < n; i++) {for (int j = m-1; j >= beat[i]; j--) {for (int l = n; l >= 1; l--) {if (dp[j-beat[i]][l-1] + w[i] > k) continue;dp[j][l] = min(dp[j][l], dp[j-beat[i]][l-1] + w[i]);}}}for (int i = n; i >= 0; i--) {for (int j = 0; j <= m-1; j++) {if (dp[j][i] != 0x3f3f3f3f) {printf("%d %d\n", i, m - j);return 0;}}}return 0;
}

区间DP:

1.ACwing252——区间DP、前缀和、贪心

思路分析:因为代价最小,一个很重要的条件就是,不会合并原始的一堆石子超过两次,否则会有交叉,必然会更多,所以可以优化到三重循环的DP做!这是因为前缀和可以帮我们剩下一层复杂度,还有枚举区间的复杂度一共两层循环。

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 307;
int dp[N][N], a[N];
int main() {int n;scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%d", &a[i]);// for (int i = 1; i <= n; i++) dp[1][i] = a[i];for (int i = 2; i <= n; i++) a[i] += a[i-1];for (int i = 2; i <= n; i++) {for (int j = 1; j <= n-i+1; j++) {dp[i][j] = 1e7;for (int k = 1; k <= i - 1; k++) {dp[i][j] = min(dp[i][j], dp[k][j]+dp[i-k][j+k]+a[j+i-1]-a[j-1]);}}}printf("%d\n", dp[n][1]);return 0;
}

树形DP:

极大独立集:

1.ACwing285没有上司的舞会——树形DP、求最大和
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <algorithm>
using namespace std;
const int N = 6e3+7;
int dp[N][2], a[N], n;
int h[N], e[N], ne[N], cnt, root;
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
set<int>st;
void dfs(int u) {dp[u][1] = a[u];for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];dfs(v);dp[u][0] += max(dp[v][0], dp[v][1]);dp[u][1] += dp[v][0];}
}
int main() {scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%d", &a[i]);memset(h, -1, sizeof h);for (int i = 0; i < n - 1; i++) {int x, y;scanf("%d%d", &x, &y);add(y, x);st.insert(x);}for (int i = 1; i <= n; i++) {if (st.count(i)) continue;else {root = i;break;}}dfs(root);printf("%d\n", max(dp[root][0], dp[root][1]));return 0;
}
2.ACwing323战略游戏——树形DP、求最小和

1 个结点时,需要注意没有边,所以不需要士兵。

#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3e3+7;
set<int>st;
int v[N], dp[N][2];
int h[N], e[N], ne[N], cnt, root;
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
void dfs(int u) {dp[u][0] = 0;dp[u][1] = 1;for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];dfs(vv);dp[u][0] += dp[vv][1];dp[u][1] += min(dp[vv][1], dp[vv][0]);// dp[u][0] += dp[vv][1];// dp[u][1] += max(dp[vv][1] - 1, dp[vv][0]);}
}
int main() {int n, x, s, y;while(scanf("%d", &n) == 1) {cnt = 0;memset(h, -1, sizeof h);st.clear();for (int i = 0; i < n; i++) {scanf("%d:(%d)", &x, &s);for (int j = 0; j < s; j++) {scanf("%d", &y);st.insert(y);add(x, y);}}for (int i = 0; i < n; i++) {if (st.count(i)) continue;else root = i;// st.erase(i);}// printf("root = %d\n", root);dfs(root);if (n == 1) {puts("0");continue ;}printf("%d\n", min(dp[root][0], dp[root][1]));}return 0;
}
3.ACwing1077皇宫看守——复杂状态的树形DP

分为三个状态。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <cstring>
#include <vector>
using namespace std;
const int N = 3e3+7;
int dp[N][4], w[N], n;
int h[N], e[N], ne[N], cnt, root;
set<int> st;
vector<int> ve[N];
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
// void dfs(int u) {//     dp[u][0] = 0;
//     dp[u][1] = w[u];
//     for (int i = h[u]; ~i; i = ne[i]) {//         int vv = e[i];
//         dfs(vv);
//         dp[u][0] += dp[vv][1];
//         dp[u][1] += min(dp[vv][0], dp[vv][1]);
//     }
// }
// void dfs(int u) {//     dp[u][0] = 0;
//     dp[u][1] = w[u];
//     for (int i = h[u]; ~i; i = ne[i]) {//         int vv = w[i];
//         ve[u].push_back(vv);
//         dfs(vv);
//         dp[u][0] += dp[vv][1];
//         if (ve[vv].size())
//             for (auto gs: ve[vv])
//                 dp[u][1] += min(dp[gs][1], dp[vv][1]);
//         else dp[u][1] += dp[vv][0];
//     }
// }
// 0: 不放,被爸爸看到,1:不放,被儿子看到,2:放
int a[N];
void dfs(int u) {dp[u][0] = 0;dp[u][2] = w[u];int sum = 0;for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];dfs(vv);dp[u][0] += min(dp[vv][1], dp[vv][2]);dp[u][2] += min(dp[vv][1], min(dp[vv][0], dp[vv][2]));a[vv] = min(dp[vv][1], dp[vv][2]);sum += a[vv];}dp[u][1] = 1e8;for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];dp[u][1] = min(dp[vv][2] + sum - a[vv], dp[u][1]);}
}
int main() {scanf("%d", &n);memset(h, -1, sizeof h);for (int i = 1; i <= n; i++) {int s, x, y;scanf("%d%d%d", &x, &y, &s); w[x] = y;for (int j = 0; j < s; j++) {scanf("%d", &y);st.insert(y);add(x, y);}}for (int i = 1; i <= n; i++) {if (st.count(i)) continue;root = i;break;}dfs(root);if (n == 1) {printf("%d\n", w[1]);return 0;}if (n == 2) {printf("%d\n", min(w[1], w[2]));return 0;}printf("%d\n", min(dp[root][2], dp[root][1]));return 0;
}

树的最长路径:

每棵树的最长路径是每个结点的第一深路径和第二深路径的和。
一边深搜,全局维护最优解即可。

1.ACwing1072——树的最长路径模板题
// 11/11 19.56 20.29
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e4+7;
int e[N], h[N], p[N], ne[N], cnt, sum;
void add(int u, int v, int w) {e[cnt] = v, ne[cnt] = h[u], p[cnt] = w, h[u] = cnt++;
}
int dfs(int u, int pa) {// printf("%d\n", u);int ans = 0, ans2 = 0;for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (v == pa) continue;int s = dfs(v, u) + p[i];if (s > ans) ans2 = ans, ans = s;else ans2 = max(ans2, s); }sum = max(sum, ans+ans2);return ans;
}
int main() {int n, u, v, w;memset(h, -1, sizeof h);scanf("%d", &n);for (int i = 0; i < n-1; i++) {scanf("%d%d%d", &u, &v, &w);add(u, v, w);add(v, u, w);}dfs(1, -1);printf("%d\n", sum);return 0;
}
2.ACwing1075——约束和NlogN处理,无根树最长路径

题目分析:难点在于快速求出数字的约束和,先枚举每个数字,之后枚举倍数来找到他的所有倍数,每次枚举到倍数,相应的倍数的sum数组就加上i。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5+7;
int dp[N], n, m;
int h[N], e[N], ne[N], sum[N], cnt, ans;
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
int dfs(int u, int pa) {int d1 = 0, d2 = 0;for (int i = h[u]; ~i; i = ne[i]) {int j = e[i];if (j == pa) continue;int s = dfs(j, u) + 1;if (s >= d1) d2 = d1, d1 = s;else if (s > d2) d2 = s;}ans = max(d1+d2, ans);return d1;
}
int main() {scanf("%d", &n);memset(h, -1, sizeof h);for (int i = 1; i <= n; i++) for (int j = 2; j <= n/i; j++)sum[i*j] += i;for (int i = 2; i <= n; i++) if (sum[i] < i) add(i, sum[i]), add(sum[i], i);dfs(1, -1);printf("%d\n", ans);return 0;
}

树的中心:

树上的一个结点:到其他结点的最远距离最小。

1.ACwing1073——上下深搜

题目分析:一棵树的一个结点到其他结点距离最远,有三种情况:

  1. 其子树的最远距离(求法见树的最长路径)。
  2. 其父结点可到的最远距离的点:
    (1)父亲结点向上搜索的最远距离+连接父子两点的边权:父亲结点的这个状态可以根据其祖父结点的向上搜索和向下搜索的最远距离+连接该点的边权进行更新,而动态规划向上搜索的初始状态就是根节点的向上搜索最远距离,即 0(dp_up[root] = 0)。编码时从根往下深搜即可。
    (2)父亲结点向下搜索的最远距离+连接父子两点的边权:这个在 1 中已经全部计算完毕。

编程思路:1 的向上搜索是用子结点的信息更新父结点,2 的向下搜索是用父结点的信息更新子节点。因为1是自底向上,2 是自顶向下。
本题的负权会误导初始化成-inf,其实0就可以,因为如果最远距离为负,那还不如哪也不去,就自己到自己的距离为 0,都比最远距离远。就相当于负权图,累计为负,所以不累计就是最佳选择!

// 11/14 8.06
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
const int inf = 0x3f3f3f3f;
int d1[N], d2[N], up[N];
int h[N], e[N], ne[N], w[N], p[N], cnt;
// 不初始化 up[] 为 -inf 是因为结点的向上搜索距离如果为负,还不如自己到自己自己最远,就是 0
// d[] 数组不初始化为 -inf 同理
void init() {memset(h, -1, sizeof h);
}
void add(int u, int v, int w_) {e[cnt] = v, ne[cnt] = h[u], w[cnt] = w_, h[u] = cnt++;
}
int dfs_d(int u, int pa) {//   d1[u] = d2[u] = -inf;for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (pa == v) continue;int s = dfs_d(v, u)+w[i];if (s >= d1[u]) d2[u] = d1[u], d1[u] = s, p[u] = v;else if (s >= d2[u]) d2[u] = s;}
//    if (d1[u] == -inf) d1[u] = d2[u] = 0;
//    else if (d2[u] == -inf) d2[u] = 0;return d1[u];
}
void dfs_u(int u, int pa) {if (pa == -1) up[u] = 0;for (int i = h[u]; ~i; i = ne[i]) {int v = e[i];if (v == pa) continue;up[v] = max(up[v], up[u]+w[i]);if (p[u] == v) up[v] = max(up[v], d2[u]+w[i]);else up[v] = max(up[v], d1[u]+w[i]);dfs_u(v, u);}
}
int main() {int n;scanf("%d", &n);init();for (int i = 0; i < n-1; i++) {int u,v,w;scanf("%d%d%d", &u, &v, &w);add(u, v, w);add(v, u, w);}dfs_d(1, -1);dfs_u(1, -1);int ans = inf;for (int i = 1; i <= n; i++) ans = min(ans, max(d1[i],up[i]));printf("%d\n", ans);return 0;
}

第四章——数据结构

堆(Heap)

平衡树

Treap

  1. 中序遍历为从小到大排列。
  2. 找一个结点前驱的方法:
    (1)如果有左子树:从左孩子开始,一直往右走。
    (2)如果不存在左子树:第一个出现的左上边通向的结点。这说明该结点是某个结点的右子树的最小值,所以该结点的前驱就是这个结点。根据二叉搜索树的性质,从该结点一直顺着父亲结点往上走,只要出现往左拐的情况,那么该边通向的结点就是第一个比它小的元素,因为往上走的时候,往右上走,到达的都是比它大的元素,只有往左上走的时候,才是第一个遇到的比它小的元素。这个元素即为它的前驱。
  3. 找一个结点后继的方法:
    (1)如果有右子树:从右孩子开始,一直往左走。
    (2)如果不存在右子树:第一个出现的右上边通向的结点。

Splay

  1. 双旋上升,使得一个结点一次上升两层,如果没有折线,则先转该点父亲,再转该点,否则,转两次该点。
  2. 双旋比起单旋,避免了链表的情况,所以,没有直线的时候,先转他的父亲,保证减少一层之后,再转本身。(三层以上有效,不包含三层)。
  3. 并非严格平衡,因为每次只是将上次查找的结点转到根节点,,如果是最小的元素转到根节点,这很明显就不是一颗平衡树。
  4. 时间复杂度:O((M+N)log(N)。(m 为操作数,n 为节点总数)。
    Splay时间复杂度详细分析1
    Splay时间复杂度详细分析2

1.ACwing253——Splay模板题前驱后继删除

参考:Splay模板讲解

// 11/13 19.35 20.13
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
const int inf = 1e8;
int id[N], n, m, cnt, root;
void init() {cnt = 0;for (int i = n+2; i >= 1; i--) id[cnt++] = i;
}
struct node {int s[2], p, v, count, size;node(){}node(int p_, int v_): p(p_), v(v_) {s[0] = s[1] = 0, count = size = 1;}
}tr[N];
void pushup(int x) {tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + tr[x].count;
}
void rotate(int x) {int y = tr[x].p, z = tr[y].p;int k = tr[y].s[1] == x, kk = tr[z].s[1] == y;tr[z].s[kk] = x, tr[x].p = z;tr[y].s[k] = tr[x].s[k^1], tr[tr[x].s[k^1]].p = y;tr[x].s[k^1] = y, tr[y].p = x;pushup(y), pushup(x);
}
void splay(int x, int k) {while (tr[x].p != k) {int y = tr[x].p, z = tr[y].p;if (z != k)(tr[y].s[1]==x)^(tr[z].s[1]==y)?rotate(x):rotate(y);rotate(x); }if (!k) root = x;
}
void insert(int x) {int u = root, p = 0, k;while (u && x != tr[u].v) p=u, k=x>tr[u].v, u = tr[u].s[k];if (u) tr[u].count++;else {u = id[--cnt]; tr[u] = node(p, x); if (p) tr[p].s[k] = u;}splay(u, 0);
}
void findx(int x) {int u = root;if (!u) return ; // tree emptywhile (tr[u].s[x>tr[u].v] && x!=tr[u].v) u = tr[u].s[x>tr[u].v];splay(u, 0);
}
int getk(int k) {int u = root;if (tr[u].size < k) return 0;while (u) {int l = tr[u].s[0], r = tr[u].s[1];if (k > tr[l].size + tr[u].count) k -= tr[l].size + tr[u].count, u = r;else if (k <= tr[l].size) u = l;else break;}return u; // 查找成功
}
int nex(int x, int f) {findx(x);int u = root;if (f && tr[u].v>x) return u;if (!f && tr[u].v<x) return u;u = tr[u].s[f];while(tr[u].s[f^1]) u=tr[u].s[f^1];return u;
}
void dele(int x) {int l = nex(x, 0), r = nex(x, 1);splay(l, 0), splay(r, l);int u = tr[r].s[0];tr[u].count--;if (!tr[u].count) id[cnt++] = u, tr[r].s[0] = 0;else splay(u, 0);
}
int main() {scanf("%d", &n);init();insert(-inf), insert(inf);int op, x;for (int i = 0; i < n; i++) {scanf("%d%d", &op, &x);if (op == 1) insert(x);else if (op == 2) dele(x);else if (op == 3) {findx(x);printf("%d\n", tr[tr[root].s[0]].size);}else if (op == 4) {int u = getk(x+1);printf("%d\n", tr[u].v);}else if (op == 5) {int u = nex(x, 0);printf("%d\n", tr[u].v);}else {int u = nex(x, 1);printf("%d\n", tr[u].v);}} return 0;
}

2.ACwing256——Splay简单应用

// 11/13 20.44 ]]]] 20.56
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 4e4+7;
const int inf = 1e8;
int id[N], cnt, root, n;
void init() {cnt = 0;for (int i = n+2; i >= 1; i--) id[cnt++] = i;
}
struct node {int s[2], p, v, size;node(){}node(int p_, int v_): p(p_), v(v_) {s[0] = s[1] = 0, size = 1;}
}tr[N];
void pushup(int x) {tr[x].size = tr[tr[x].s[0]].size + 1 + tr[tr[x].s[1]].size;
}
void rotate(int x) {int y = tr[x].p, z = tr[y].p;int k = tr[y].s[1] == x, kk = tr[z].s[1] == y;tr[z].s[kk] = x, tr[x].p = z;tr[y].s[k] = tr[x].s[k^1], tr[tr[x].s[k^1]].p = y;tr[x].s[k^1] = y, tr[y].p = x;pushup(y), pushup(x);
}
void splay(int x, int k) {while (tr[x].p != k) {int y = tr[x].p, z = tr[y].p;if (z != k) (tr[y].s[0]==x)^(tr[z].s[0]==y)? rotate(x): rotate(y);rotate(x);}if (!k) root = x;
}
void insert(int x) {int u = root, p = 0, k;while (u && x != tr[u].v) {p = u, k = x>tr[u].v;u = tr[u].s[k]; }if (!u) {u = id[--cnt], tr[u] = node(p, x);if (p) tr[p].s[k] = u;}splay(u, 0);
}
void findx(int x) {int u = root;if (!u) return ;while (tr[u].s[x>tr[u].v] && x != tr[u].v) u = tr[u].s[x>tr[u].v];splay(u, 0);
}
int nex(int x, int f) {findx(x);int u = root;// printf("u=%d\n", u);if (!f && tr[u].v <= x) return u;if (f && tr[u].v >= x) return u;u = tr[u].s[f];while (tr[u].s[f^1]) u = tr[u].s[f^1];return u;
}
void output(int u) {if (!u) return ;output(tr[u].s[0]);if (tr[u].v > -inf && tr[u].v <= inf)printf("%d ", tr[u].v);output(tr[u].s[1]);
}
int main() {scanf("%d", &n);init();int x, ans = 0;insert(-inf), insert(inf);for (int i = 0; i < n; i++) {scanf("%d", &x);if (!i) ans += x;else {int l = nex(x, 0), r = nex(x, 1);ans += min(abs(x-tr[l].v), abs(x-tr[r].v));// printf("l=%d, r=%d, ans=%d\n", //         l, r, min(abs(x-tr[l].v), abs(x-tr[r].v)));}insert(x);// output(root);// puts("");}printf("%d\n", ans);return 0;
}

3.ACwing1063——并查集、Splay合并

Splay合并操作:Nlog2N 的复杂度,记录每个 Splay 根的数组 root[N] 只记录并查集维护的祖先。所以函数只传 b,不传 root[b]。因为不是维护祖先所在 Splay 的根再根(因为 Splay 结点的序号不等价于题目中结点序号,所以只传并查集维护的祖先),而是维护祖先结点所在 Splay 的根。
并查集:维护两个结点的连通性。如果连通,不合并;否则使用启发式合并将小的树合并到大的树;
Splay合并:每次插入时,insert()传入的应该是 b,这个结点,而不是 root[b],否则 insert 内部会再取一遍 root,变成 root[root[b]],这里 root[b] 代表并查集中祖先结点所在 Splay 的根的序号,而再取一遍 root,会变成 Splay 中结点的序号所在的 Splay 的根,而初始化的根是结点序号,并非是 Splay 结点所分配的序号,这是难点。

#include <iostream> // 8.52
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e5+7;
struct node {int idx, v, p, s[2], count, size;node(){}node(int idx_, int v_, int p_): idx(idx_), v(v_), p(p_) { count = size = 1; s[0] = s[1] = 0;}
}tr[N];
int p[N], ids[N], idc, root[N], n, m;
void init() {for (int i = N-1; i >= 1; i--) ids[idc++] = i;
}
int find(int x) {return x == p[x] ? x : p[x] = find(p[x]);
}
void pushup(int x) {tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + tr[x].count;
}
void rotate(int x) {int y = tr[x].p, z = tr[y].p;int k = x == tr[y].s[1], kk = y == tr[z].s[1];tr[z].s[kk] = x, tr[x].p = z;tr[y].s[k] = tr[x].s[k^1], tr[tr[x].s[k^1]].p = y;tr[x].s[k^1] = y, tr[y].p = x;pushup(y), pushup(x);
}
void splay(int x, int k, int b) {while (tr[x].p != k) {int y = tr[x].p, z = tr[y].p;if (z != k) (tr[y].s[1] == x)^(tr[z].s[1] == y) ? rotate(x): rotate(y);rotate(x);}if (!k) root[b] = x;
}
void insert(int x, int id, int b) {int u = root[b], p;while(u && x != tr[u].v) p = u, u = tr[u].s[x>tr[u].v];if (u) tr[u].count++;else {u = ids[--idc];tr[u] = node(id, x, p);if (p) tr[p].s[x>tr[p].v] = u;}splay(u, 0, b);
}
int getk(int k, int b) {int u = root[b]; while (u) {int l = tr[u].s[0], r = tr[u].s[1];if (k > tr[l].size + tr[u].count) k -= tr[l].size + tr[u].count, u = r;else if (k <= tr[l].size) u = l;else return u;}return 0;
}
void dfs(int u, int b) {if (tr[u].s[0]) dfs(tr[u].s[0], b);if (tr[u].s[1]) dfs(tr[u].s[1], b);insert(tr[u].v, tr[u].idx, b);ids[idc++] = u;
}
int main() {scanf("%d%d", &n, &m);init();for (int i = 1; i <= n; i++) {p[i] = root[i] = i;int v;scanf("%d", &v);tr[ids[--idc]] = node(i, v, 0); // 0 代表 没有父结点,即根结点}while(m--) {int a, b;scanf("%d%d", &a, &b);a = find(a), b = find(b);if (tr[root[a]].size > tr[root[b]].size) swap(a, b);if (a != b) dfs(root[a], b);p[a] = b;}char op[3]; int x, k;scanf("%d", &m);while (m--) {scanf("%s%d%d", op, &x, &k);if (*op == 'B') {x = find(x), k = find(k);if (tr[x].size > tr[k].size) swap(x, k);if (x != k) dfs(root[x], k);p[x] = k;}else {x = find(x);if (k > tr[root[find(x)]].size) puts("-1");else {printf("%d\n", getk(k, find(x)));}}}return 0;
}

分块

将整个区间分成 logN 段,分别使用数组维护段内的数值。
完整段:直接数组维护,暂不操作整个段(懒标记)。
段内:暴力枚举维护。O(logN)。
实现手段:设置映射:完成点到段的索引获取。i/len(sqrt(N))。

1.ACwing243——分块求和

#include <iostream>
#include <algorithm>
// #include <cstring>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 1e5+7, M = 350;
ll w[N], add[M], sum[M];
int len, n, m;
int get(int x) {return x/len;
}
void change(int l, int r, int d) {if (get(l) == get(r)) {for (int i = l; i <= r; i++) w[i] += d, sum[get(i)] += d;}else {int i = l, j = r;while (get(i) == get(l)) w[i] += d, sum[get(i)] += d, i++;while (get(j) == get(r)) w[j] += d, sum[get(j)] += d, j--;for (int k = get(i); k <= get(j); k++) add[k] += d, sum[k] += len*d;}
}
ll query(int l, int r) {ll ans = 0;if (get(l) == get(r)) for (int i = l; i <= r; i++) ans += w[i] + add[get(i)];else {int i = l, j = r;while (get(i) == get(l)) ans += w[i] + add[get(i)], i++;while (get(j) == get(r)) ans += w[j] + add[get(j)], j--;for (int k = get(i); k <= get(j); k++) ans += sum[k]; }return ans;
}
int main() {scanf("%d%d", &n, &m);len = sqrt(n);for (int i = 1; i <= n; i++) {scanf("%lld", &w[i]);sum[get(i)] += w[i];}char op[2];int l, r, d;while (m--) {scanf("%s%d%d", op, &l, &r);if (*op == 'C') {scanf("%d", &d);change(l, r, d);}else {ll t = query(l, r);printf("%lld\n", t);}}return 0;
};

分块——块状链表

图论

最大流

最大流判定(二分框架)

1.ACwing2277——最大流判定、二分

1.根据二分枚举答案区间,如果dinic() >= K,就缩小答案,直到无解,如果无解,扩大答案,知道满足答案。
2.每条边流量如果大于答案,就是0,不能走,如果小于等于答案,就是1,代表可以走。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 8e4+7, INF = 1e8;
int h[N], e[N], f[N], w[N], ne[N], cur[N], d[N], cnt;
int n, m, S, T, K;
void add(int u, int v, int c) {e[cnt] = v, w[cnt] = c, ne[cnt] = h[u], h[u] = cnt++;e[cnt] = u, w[cnt] = c, ne[cnt] = h[v], h[v] = cnt++;
}
bool bfs() {int q[N], hh = 0, tt = -1;memset(d, -1, sizeof(d));q[++tt] = S, d[S] = 0, cur[S] = h[S];while (hh <= tt) {int u = q[hh++];for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];if (d[vv] == -1 && f[i]) {cur[vv] = h[vv];d[vv] = d[u] + 1;if (vv == T) return true;q[++tt] = vv;}}}return false;
}
int dfs(int u, int limit) {if (u == T) return limit;int flow = 0;for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {int vv = e[i];cur[u] = i;if (d[vv] == d[u] + 1 && f[i]) {int t = dfs(vv, min(limit - flow, f[i]));if (!t) d[vv] = -1;else f[i] -= t, f[i^1] += t, flow += t;}}return flow;
}
int dinic() {int flow = 0, r = 0;while (bfs()) while(flow = dfs(S, INF)) r += flow;// printf("flow=%d\n", r);return r;
}
bool check(int mid) {for (int i = 0; i < cnt; i++)if (w[i] > mid) f[i] = 0;else f[i] = 1;return dinic() >= K;
}
int main() {scanf("%d%d%d", &n, &m, &K);S = 1, T = n;memset(h, -1, sizeof(h));while (m--) {int a, b, c;scanf("%d%d%d", &a, &b, &c);add(a, b, c);}int l = 1, r = 1e6;while (l < r) {int mid = (l+r)>>1;if (check(mid)) r = mid;else l = mid + 1;// printf("check=%d\n", check(mid));}printf("%d\n", r);return 0;
}

分层图

1.ACwing2187——分层图、顺序枚举最大流判定

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = (22*50*2)*400+2, INF = 1e8+7;
int h[N], e[N], ne[N], f[N], cur[N], d[N], p[N], cnt;
int n, m, S, T, K;
void add(int u, int v, int c) {e[cnt] = v, f[cnt] = c, ne[cnt] = h[u], h[u] = cnt++;e[cnt] = u, f[cnt] = 0, ne[cnt] = h[v], h[v] = cnt++;
}
struct Ship{int h, r, ids[30];
}ships[30];
int find(int x) {return x == p[x] ? p[x] : p[x] = find(p[x]);
}
int get(int day, int i) {return day*(n+2) + i;
}
bool bfs() {memset(d, -1, sizeof(d));int q[N], hh = 0, tt = -1;q[++tt] = S, d[S] = 0, cur[S] = h[S];while (hh <= tt) {int u = q[hh++];for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];if (d[vv] == -1 && f[i]) {d[vv] = d[u] + 1;cur[vv] = h[vv];if (vv == T) return true;q[++tt] = vv;}}}return false;
}
int dfs(int u, int limit) {if (u == T) return limit;int flow = 0;for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {int vv = e[i];cur[u] = i;if (d[vv] == d[u] + 1 && f[i]) {int t = dfs(vv, min(f[i], limit - flow));if (!t) d[vv] = -1;else f[i] -= t, f[i^1] += t, flow += t;}}return flow;
}
int dinic() {int flow, r = 0;while(bfs()) while(flow = dfs(S, INF)) r += flow;return r;
}
int main() {scanf("%d%d%d", &n, &m, &K);for (int i = 0; i <= n+1; i++) p[i] = i;S = N - 2, T = N - 1;memset(h, -1, sizeof(h));for (int i = 0; i < m; i++) {int h, r;scanf("%d%d", &h, &r);ships[i] = {h, r};for (int j = 0; j < r; j++) {int id;scanf("%d", &id);if (id == -1) id = n+1;ships[i].ids[j] = id;if (j)p[find(id)] = find(ships[i].ids[j-1]); // !!// printf("%d ", ships[i].ids[j]);}// puts("");}// for (int i = 0; i <= n + 1; i++)//     printf("p[%d] = %d\n", i, p[i]);if (find(0) != find(n+1)) puts("0");else {int day = 0, ans = 0;add(S, get(day, 0), K);// printf("S=%d\n", S);add(get(day, n+1), T, INF);while (++day) {// printf("day=%d\n", day);add(get(day, n+1), T, INF);for (int i = 0; i <= n+1; i++) add(get(day-1, i), get(day, i), INF);for (int i = 0; i < m; i++) {int r = ships[i].r;int a = ships[i].ids[(day-1)%r], b = ships[i].ids[day%r];add(get(day-1, a), get(day, b), ships[i].h);// printf("%d %d\n", a, b);}ans += dinic();// printf("ans=%d, k=%d\n", ans, K);if (ans >= K) break;// printf("%d\n", ans);// if (day >= 10) break;}printf("%d\n", day);}return 0;
}

拆点

1.ACwing2240——拆点

当点的数量受限制时,将点拆成出点和入点两个点,中间用流量限制数量即可。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 507, M = 1000000+307, INF = 1e8+7;
int h[N], e[M], ne[M], f[M], cur[N], d[N], cnt;
int S, T, n, m, F, D;
void add(int u, int v, int c) {e[cnt] = v, f[cnt] = c, ne[cnt] = h[u], h[u] = cnt++;e[cnt] = u, f[cnt] = 0, ne[cnt] = h[v], h[v] = cnt++;
}
bool bfs() {memset(d, -1, sizeof(d));int q[N], hh = 0, tt = -1;q[++tt] = S, d[S] = 0, cur[S] = h[S];while (hh <= tt) {int u = q[hh++];for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];if (d[vv] == -1 && f[i]) {d[vv] = d[u] + 1;cur[vv] = h[vv];if (vv == T) return true;q[++tt] = vv;}}}return false;
}
int dfs(int u, int limit) {if (u == T) return limit;int flow = 0;for (int i = cur[u]; ~i && flow < limit; i = ne[i]) {int vv = e[i];cur[u] = i;if (d[vv] == d[u] + 1 && f[i]) {int t = dfs(vv, min(f[i], limit - flow));if (!t) d[vv] = -1;else f[i] -= t, f[i^1] += t, flow += t;}}return flow;
}
int dinic() {int flow, r = 0;while(bfs()) while(flow = dfs(S, INF)) r += flow;return r;
}
int main() {scanf("%d%d%d", &n, &F, &D);memset(h, -1, sizeof(h));S = 0, T = 2*n + F + D + 1;for (int i = 1; i <= n; i++) {int ff, dd, x;scanf("%d%d", &ff, &dd);for (int j = 1; j <= ff; j++) {scanf("%d", &x);add(x, F + i, 1);}add(F + i, F + i + n, 1);for (int j = 1; j <= dd; j++) {scanf("%d", &x);add(F + i + n, F + 2*n + x, 1);}}for (int i = 1; i <= F; i++) add(S, i, 1);for (int i = F + 2*n + 1; i <= F + 2*n + D; i++) add(i, T, 1);printf("%d\n", dinic());return 0;
};

有向图的强连通分量

1.ACwing1174——tarjan、缩点法

将每个强连通分量看作一个点,并记录这个连通分量中点的个数,另外还需要记录每个强连通分量到其他强连通分量的出度,这样最后判断出度为 0 的点是否只有一个即可。一个的话,输出那个点代表的强连通分量中点的个数。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5+7;
int h[N], e[N], ne[N], cnt, n, m;
int stk[N], top, in_stk[N], dfn[N], low[N], id[N], Size[N], dout[N], scc_cnt, timestep;
void init() {cnt = scc_cnt = timestep = top = 0;memset(h, -1, sizeof(h));memset(Size, 0, sizeof(Size));memset(dout, 0, sizeof(dout));
}
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
void tarjan(int u) {dfn[u] = low[u] = ++timestep;stk[top++] = u, in_stk[u] = 1;for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];if (!dfn[vv]) {tarjan(vv);low[u] = min(low[u], low[vv]);} else if (in_stk[vv]) low[u] = min(low[u], dfn[vv]);}if (dfn[u] == low[u]) {while (top) {int vv = stk[--top];in_stk[vv] = 0;id[vv] = scc_cnt;Size[scc_cnt]++;if (dfn[vv] == low[vv]) break;}scc_cnt++;}
}
int main() {scanf("%d%d", &n, &m);init();while (m--) {int a, b;scanf("%d%d", &a, &b);add(a, b);}for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);for (int i = 1; i <= n; i++) {for (int j = h[i]; ~j; j = ne[j]) {int vv = e[j];if (id[i] != id[vv]) dout[id[i]]++;}}int zero = 0, ans = 0;for (int i = 0; i < scc_cnt; i++) {if (!dout[i]) {ans = Size[i];zero++;if (zero > 1) {ans = 0;break;}}}printf("%d\n", ans);return 0;
}

无向图的双连通分量

边的双连通分量

根据桥缩点,直接枚举边即可。

1.ACwing395——边的双连通分量、缩点

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2e4+7;
int h[N], e[N], ne[N], cnt, n, m;
int dfn[N], low[N], stk[N], id[N], is_edge[N], din[N], top, dcc_cnt, timestep;
void init() {cnt = 0;memset(h, -1, sizeof h);
}
void add(int u, int v) {e[cnt] = v, ne[cnt] = h[u], h[u] = cnt++;
}
void tarjan(int u, int fa) {dfn[u] = low[u] = ++timestep;stk[top++] = u;for (int i = h[u]; ~i; i = ne[i]) {int vv = e[i];if (vv == fa) continue;if (!dfn[vv]) {tarjan(vv, u);if (low[vv] > dfn[u]) is_edge[i] = is_edge[i^1] = true;low[u] = min(low[u], low[vv]);} low[u] = min(low[u], low[vv]);}if (dfn[u] == low[u]) {while (top) {int vv = stk[--top];id[vv] = dcc_cnt;if (dfn[vv] == low[vv]) break;}dcc_cnt++;}
}
int main() {scanf("%d%d", &n, &m);init();while (m--) {int a, b;scanf("%d%d", &a, &b);add(a, b);add(b, a);}for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i, 0);// for (int u = 1; u <= n; u++) {//     for (int i = h[u]; ~i; i = ne[i]) {//         int vv = e[i];//         din[id[vv]]++;//     }// }for (int i = 0; i < cnt; i++) if (is_edge[i]) din[id[e[i]]]++;int ans = 0;for (int i = 0; i < dcc_cnt; i++) {if (din[i] == 1) ans++;}printf("%d\n", (ans+1)/2);return 0;
}

数学

扩展欧几里得定理

推导:ax+by = gcd(a, b) = gcd(b, a%b)
所以得到: by+(a%b)x --> by+(a-a/b * b)x
整理得到: ax+b(y-a/b*x) = gcd(a,b)

1.ACwing877——扩展欧几里得定理

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int gcd(int &x, int &y, int a, int b) {if (!b) {x = 1, y = 0;return a;}int d = gcd(y, x, b, a%b); // ax+by=d  by+(a-a/b*b)x=d y = y - a/b*x;return d;
}
int main() {int x, y, a, b;int T;scanf("%d", &T);while (T--) {scanf("%d%d", &a, &b);gcd(x, y, a, b);printf("%d %d\n", x, y);}return 0;
}

2.ACwing878——线性同余方程

题目理解:使用扩展欧几里得算法求出 x,但是对应到 b 需要 b 是最大公约数的倍数,且扩大的过程中可能超过 int 范围,所以需要最后模 m。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
// ax+by=d  by+(a-a/b*b)x=d
int gcd(int a, int b, int &x, int &y) {if (!b) {x = 1, y = 0;return a;}int d = gcd(b, a%b, y, x);y -= a/b*x;return d;
}
// ax-my=b
int main() {int T, a, b, m, x, y, d;scanf("%d", &T);while (T--) {scanf("%d%d%d", &a, &b, &m);if(b%(d = gcd(a, m, x, y)) != 0) puts("impossible");else printf("%d\n", 1ll*x*(b/d)%m);}return 0;
}

欧拉函数

证明:根据唯一分解定理,容斥原理。
f(N)=N(1-1/p1)(1-1/p2)(1-1/pk)。

1.ACwing873——欧拉函数

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int main() {int n, T;scanf("%d", &T);while (T--) {scanf("%d", &n);int res = n;for (int i = 2; i <= n/i; i++) if (n%i == 0) {res = res/i*(i-1);while (n%i == 0) n /= i;}if (n > 1) res = res/n*(n-1);printf("%d\n", res);}return 0;
}

算法入门经典模板总结相关推荐

  1. 【算法笔记】莫比乌斯反演(包含定理,两种形式的证明及入门经典模板)

    整理的算法模板合集: ACM模板 目录 一.莫比乌斯反演 二.几个概念和定理 三.两种形式的莫比乌斯反演证明 四.POJ 3904 Sky Code(入门例题) 一.莫比乌斯反演 学习笔记,我是看这个 ...

  2. 《算法入门经典大赛——培训指南》第二章考试

    UVa特别考试 UVa站点专门为本书设立的分类题库配合,方便读者提交: http://uva.onlinejudge.org/index.php?option=com_onlinejudge& ...

  3. 算法入门经典-第七章 例题7-2 八皇后问题

    原本利用回溯思想解决的经典八皇后问题,其实也是可以用递归解决的~ 八皇后的递归解决思路: 从第一行开始,依次判断0~8列的哪一列可以放置Queen,这样就确定了该行的Queen的位置,然后行数递增,继 ...

  4. 算法入门经典-第七章 例题7-2-2 可重集的排列

    补充:如果某步骤的解可以由多个步骤得到,而每个步骤都有若干种选择,这些候选方式可能依赖于之前的选择, 且可以用递归枚举法实现,则它的工作方式可以用解答树来描述 可重:如果问题变成输入数组p,并按字典序 ...

  5. 算法入门经典第六章 例题6-8 树

    题意: 给一棵点带权的二叉树的中序和后序遍历,找一个叶子使得它到根的路径上的权值的和最小,如果多解,那该叶子本身的权值应该最小 解题思路: 1.用getline()输入整行字符,然后用stringst ...

  6. 算法入门经典第六章 例题6-2 铁轨

    题目描述 某城市有一个火车站,铁轨铺设如图所示.有n节车厢从A方向驶入车站,按进站顺序编号为1~n.你的任务是让它们按照某种特定的顺序进入B方向的铁轨并驶出车站.为了重组车厢,你可以借助中转站C.这是 ...

  7. 算法入门经典 第三章

    scanf 遇到tab或空格或换行符停下来 1.例题2-1 7744问题 从数本身看 从个位数的数字看 #include <iostream> #include<math.h> ...

  8. 算法入门经典习题第一章

    习题1-1 平均数(average) #include<iostream> #include<bits/stdc++.h> using namespace std; int m ...

  9. 算法入门经典-第七章 例题7-2最大乘积

    最大乘积 输入n个元素组成的序列s,你需要找出一个乘积最大的连续子序列.如果这个最大的成绩不是正数,应输入-1(表示无解).输入0结束输入.1<=n<=18,-10<=Si<= ...

最新文章

  1. 《C++入门经典(第5版•修订版)》——2.6 问与答
  2. pythonunbuffered_python所遇到的坑
  3. advanced installer更换程序id_浅谈更换调频发射机EEPROM的方法和步骤
  4. oracle最大空闲时间,使用Oracle PROFILE控制会话空闲时间
  5. 美摄云非编系统——网页端实时编辑渲染方案
  6. java监控数据库的增量_【安德鲁斯】基于脚本的数据库quot;增量更新quot;,如果不改变,每次更新java代码、!...
  7. anjular.js表单验证实例
  8. css3 下边框缓缓划过_CSS3 框大小
  9. 深度学习要多深_才能读懂人话?
  10. 2017年苹果企业开发者账号申请完整指南
  11. Flash 平台音视频直播的实现
  12. java Random类和Math.Rondom
  13. 广东计算机科学导论考试试卷,计算机科学导论试题A答案
  14. matlab 外接圆,【外接圆matlab知道三个顶点的坐标,如何求这三个顶点组成的三角形外接圆的半径与圆心坐标?】作业帮...
  15. 【计算理论】正则语言 ( 正则语言运算 | 正则语言封闭性 )
  16. 国产UOS系统之——安装N卡驱动(多屏显示)
  17. Windows系统IPC$共享与勒索病毒
  18. C#压缩图片文件大小
  19. [编程题] 翻转数列--附详细分析思路
  20. mt2503 Phonebook 开机初始化过程

热门文章

  1. php文件域的作用,在word中何为域
  2. 大数据开发,就要掌握哪些技术?
  3. ?nocache=+new Date().getTime();中的nocache是什么意思
  4. “别具一格”的vue双向数据绑定原理
  5. 自学php看什么视频,PHP自学要多久?看了这11部php视频教程,你就是高手
  6. 通过不断重置学习率来逃离局部极值点
  7. 读书笔记: C# 7.0 in a nutshell (第 三 章 Creating Types in C#)
  8. java丐帮_java多线程学习笔记(二)
  9. 计算机怎么把安全设置降低,如何设置浏览器的安全级别,怎么降低浏览器安全级别...
  10. 文献速递第3期:tDCS的近期研究