[数据结构] [算法] [PEP总结] C++信奥常用模板总结
杂项模板
快读快写(__int128适用)
点击查看代码
#include <iostream>
using namespace std;
typedef __int128 LLL;
LLL read() {
LLL x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
void out(LLL x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) out(x / 10);
putchar(x % 10 + '0');
}
int n;
int main() {
cin >> n;
__int128 a, b;
char c;
for (int i = 1; i <= n; i++) {
a = read();
cin >> c;
b = read();
if (c == '+') {
out(a + b);
cout << endl;
}
if (c == '-') {
out(a - b);
cout << endl;
}
}
return 0;
}
筛法求素数
点击查看代码
//埃氏筛 O(n log log n);
#include <iostream>
#include <cstring>
using namespace std;
int n;
int vis[100000005];
int main() {
cin >> n;
memset(vis, 0, sizeof(vis));
for (int i = 2; i <= n; i++) {
if (vis[i]) continue;
cout << i << endl;
for (int j = i; j <= n / i; j++) vis[i * j] = 1;
}
return 0;
}
//线性筛 O(n);
#include <iostream>
#include <cstdio>
using namespace std;
int pri[100000005], cnt;
bool vis[100000005];
void w(int x) {
for (int i = 2; i <= x; i++) {
if (!vis[i]) pri[++cnt] = i;
for (int j = 1; j <= cnt && i * pri[j] <= x; j++) {
vis[i * pri[j]] = true;
if (i % pri[j] == 0) break;
}
}
cout << cnt << endl;
for (int i = 1; i <= cnt; i++) cout << pri[i] << ' ';
}
int main() {
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
w(100000000);
return 0;
}
高精度
点击查看代码
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
char a[10005], b[10005];
int a1[10005], b1[10005], c[10005];
int lena, lenb, lenc;
int x; //进位;
void gao_jing_jia(int a1[], int b1[]) {
memset(c, 0, sizeof(c));
lenc = max(lena, lenb);
x = 0;
for (int i = 0; i <= lenc - 1; i++) {
c[i] = a1[i] + b1[i] + x;
x = c[i] / 10;
c[i] %= 10;
}
if (x) {
lenc++;
c[lenc - 1] = x;
}
}
void gao_jing_jian(int a1[], int b1[]) {
memset(c, 0, sizeof(c));
lenc = max(lena, lenb);
x = 0;
if ((lena < lenb) || ((lena == lenb) && a1[lena - 1] < b1[lenb - 1])) {
cout << '-';
for (int i = 0; i <= lenc - 1; i++) {
if (b1[i] - a1[i] < 0) {
b1[i + 1]--;
b1[i] += 10;
}
c[i] = b1[i] - a1[i];
}
while(!c[lenc - 1]) {
if (lenc - 1 == 0) break;
lenc--;
}
} else {
for (int i = 0; i <= lenc - 1; i++) {
if (a1[i] - b1[i] < 0) {
a1[i + 1]--;
a1[i] += 10;
}
c[i] = a1[i] - b1[i];
}
while(!c[lenc - 1]) {
if (lenc - 1 == 0) break;
lenc--;
}
}
}
void gao_jing_cheng(int a1[], int b1[]) {
if (((lena == 1) && a1[0] == 0) || ((lenb == 1) && b1[0] == 0)) {
c[0] = 0;
lenc = 1;
return;
}
lenc = lena + lenb;
for (int i = 0; i <= lena - 1; i++) {
for (int j = 0; j <= lenb - 1; j++) {
c[i + j] += a1[i] * b1[j];
c[i + j + 1] += c[i + j] / 10;
c[i + j] %= 10;
}
}
while(!c[lenc - 1]) lenc--;
}
int main() {
cin >> a >> b;
lena = strlen(a);
lenb = strlen(b);
for (int i = 0; i < lena; i++) { //倒着存,方便进位;
a1[i] = a[lena - i - 1] - '0';
}
for (int i = 0; i < lenb; i++) {
b1[i] = b[lenb - i - 1] - '0';
}
// gao_jing_jia(a1, b1);
// gao_jing_jian(a1, b1);
// gao_jing_cheng(a1, b1);
for (int i = lenc - 1; i >= 0; i--) {
cout << c[i];
}
return 0;
}
归并排序与逆序对
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int a[5000005]; //待排序数组;
int r1[5000005]; //排好序数组;
int n;
long long ans; //注意long long;
void merge_sort(int l, int r) {
if (l == r) return;
int m = (l + r) >> 1;
int i, j, k;
merge_sort(l, m);
merge_sort(m + 1, r);
i = l;
k = l;
j = m + 1;
while(i <= m && j <= r) {
if (a[i] <= a[j]) { //注意等号,会影响下面的逆序对个数;
r1[k] = a[i]; //r[k]每次都存较小的那一个;
k++;
i++;
}
if (a[i] > a[j]) {
r1[k] = a[j];
k++;
j++;
ans += m - i + 1; //逆序对个数,因为r数组已部分排好序(从中点出发),所以m - i + 1就是逆序对个数;
}
}
while(i <= m) { //将左边剩余元素接入r数组中;
r1[k] = a[i];
i++;
k++;
}
while(j <= r) { //将右边剩余元素接入r数组中;
r1[k] = a[j];
j++;
k++;
}
for (int i = l; i <= r; i++) {
a[i] = r1[i]; //将a数组排为有序;
}
}
int main() {
scanf("%d", &n); //不要用cin,否则可能会超时;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
merge_sort(1, n);
for (int i = 1; i <= n; i++) {
cout << a[i] << ' ';
}
printf("\n");
printf("%lld", ans);
return 0;
}
二分查找与二分答案
点击查看代码
#include <iostream>
using namespace std;
int a[10005];
int n;
int l, r;
int y;
bool check(int x) {
if (a[x] >= y) return true;
else return false;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> y; //待查找元素;
l = 1;
r = n;
while(l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
r = mid - 1;
} else {
l = mid + 1;
}
}
if (a[l] != y) {
cout << "No Answer!";
return 0;
}
cout << l;
return 0;
}
快速幂取模
输出 $ a^b \mod c $ 的值;
点击查看代码
#include <iostream>
using namespace std;
long long a, b, c;
long long kuai_su_mi(long long a, long long b, long long c) {
long long ans = 1;
while (b > 0) {
if (b & 1) {
ans = (ans * a) % c;
}
a = a * a % c;
b >>= 1;
}
return ans;
}
int main() {
cin >> a >> b >> c;
cout << kuai_su_mi(a, b, c);
return 0;
}
图论模板
<1> 树基础
二叉树的前,中,后序遍历
点击查看代码
#include <iostream>
using namespace std;
int n;
struct sss{ //树的存储;
char v;
int ls, rs; //节点值,左孩子(编号),右孩子(编号);
}tr[100005];
void xian(int x) { //根左右;
if (x != 0) {
cout << tr[x].v;
xian(tr[x].ls);
xian(tr[x].rs);
}
}
void zhong(int x) { //左根右;
if (x != 0) {
zhong(tr[x].ls);
cout << tr[x].v;
zhong(tr[x].rs);
}
}
void hou(int x) { //左右根;
if (x != 0) {
hou(tr[x].ls);
hou(tr[x].rs);
cout << tr[x].v;
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> tr[i].v;
cin >> tr[i].ls >> tr[i].rs;
}
xian(1);
cout << endl;
zhong(1);
cout << endl;
hou(1);
cout << endl;
return 0;
}
普通树转二叉树
点击查看代码
#include <iostream>
using namespace std;
int n;
struct sss{
char a;
int ls, rs;
}e[100005];
void qian(int x) {
if (x) {
cout << e[x].a;
qian(e[x].ls);
qian(e[x].rs);
}
}
void hou(int x) {
if (x) {
hou(e[x].ls);
hou(e[x].rs);
cout << e[x].a;
}
}
int main() {
cin >> n;
int o = 1;
int b;
int p;
for (int i = 1; i <= n; i++) {
cin >> e[i].a;
cin >> b;
if (b != 0) {
e[i].ls = b; //将与根节点链接的第一个节点作为此节点的左孩子;
}
p = b;
while (b != 0) {
cin >> b;
if (b != 0) { //有可能b == 0,所以要特判;
e[p].rs = b; //加边,原来的兄弟变为右孩子; (同时也删了边(兄弟和根节点的边));
}
p = b;
}
}
qian(1);
cout << endl;
hou(1);
return 0;
} //最后旋转处理(在纸上)就可得到一个二叉树;
已知中后求前
点击查看代码
#include <iostream>
#include <string>
using namespace std;
string a, b;
void zhong_hou_qiou_qian(string x, string y) {
int xl = x.size(), yl = y.size();
cout << y[yl - 1];
if (yl == 1) return; //只有根节点;
int k = x.find(y[yl - 1], 0);
if (k > 0) {
string s1 = x.substr(0, k);
string s2 = y.substr(0, k);
zhong_hou_qiou_qian(s1, s2);
}
if (k < yl - 1) {
string s3 = x.substr(k + 1, xl - k - 1);
string s4 = y.substr(yl - s3.size() - 1, s3.size());
zhong_hou_qiou_qian(s3, s4);
}
}
int main() {
cin >> a >> b;
zhong_hou_qiou_qian(a, b);
return 0;
}
已知前中求后
点击查看代码
#include <iostream>
#include <string>
using namespace std;
string a, b;
int s;
void qian_zhong_qiou_hou(int l, int r) {
if (l > r) return; //当l == r时还能再找根(下标为l);不能写l >= r;
for (int i = l; i <= r; i++) {
if (a[s] == b[i]) { //如果在b中找到根;
s++; //更新根;
qian_zhong_qiou_hou(l, i - 1);
qian_zhong_qiou_hou(i + 1, r); //递归时不能再有此根;
cout << b[i]; //左右根;
}
}
}
int main() {
cin >> a >> b;
s = 0;
qian_zhong_qiou_hou(0, a.size() - 1);
return 0;
}
注:已知前后不能求中,因为树不是唯一确定的;
二叉排序树(中序遍历有序)
左子树的所有节点比此节点小,右子树的所有节点比此节点大
点击查看代码
板子(无注释):
#include <iostream>
using namespace std;
struct sss{
int a;
int ls, rs;
}e[1000005];
int n;
void zhong(int x) {
if (x) {
zhong(e[x].ls);
cout << e[x].a << ' ';
zhong(e[x].rs);
}
}
void hou(int x) {
if (x) {
hou(e[x].ls);
hou(e[x].rs);
cout << e[x].a << ' ';
}
}
int main() {
cin >> n;
int x = 0;
int p = 0;
for (int i = 1; i <= n; i++) {
cin >> e[i].a;
x = e[i].a;
if (i == 1) continue;
p = 1;
while(1) {
if (x < e[p].a) {
if (e[p].ls != 0) {
p = e[p].ls;
continue;
} else {
e[p].ls = i;
break;
}
} else {
if (e[p].rs != 0) {
p = e[p].rs;
continue;
} else {
e[p].rs = i;
break;
}
}
}
}
zhong(1);
cout << endl;
hou(1);
return 0;
}
最优二叉树(哈夫曼树 Huffman Tree)
带权路径长度之和达到最小的二叉树
点击查看代码
板子(无注释):
#include <iostream>
using namespace std;
long long n;
struct sss{
long long a;
long long ls, rs;
}e[100005];
sss eg[100005];
int k;
long long ma() {
long long t = -1;
for (int i = 1; i <= n; i++) {
if (e[i].a > t) {
t = e[i].a;
k = i;
}
}
e[k].a = -1;
return t;
}
long long mi() {
long long t = 0xfffffffffffff;
for (int i = 1; i <= n; i++) {
if (eg[i].a < t) {
t = eg[i].a;
k = i;
}
}
eg[k].a = 0xfffffffffff;
return t;
}
void ma_t() {
for (int i = n + 1; i <= 2 * n - 1; i++) {
e[i].ls = ma();
e[i].rs = ma();
e[i].a = e[i].ls * e[i].rs + 1;
e[k].a = e[i].a;
}
}
void mi_t() {
for (int i = n + 1; i <= 2 * n - 1; i++) {
eg[i].ls = mi();
eg[i].rs = mi();
eg[i].a = eg[i].ls * eg[i].rs + 1;
eg[k].a = eg[i].a;
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> e[i].a;
eg[i].a = e[i].a;
}
mi_t();
ma_t();
cout << eg[2 * n - 1].a << endl;
cout << e[2 * n - 1].a << endl;
cout << eg[2 * n - 1].a - e[2 * n - 1].a;
return 0;
}
<2> 图的最短路(注意初始化)
邻接矩阵
点击查看代码
#include <iostream>
using namespace std;
int n;
int a[1005][1005];
int main() {
cin >> n;
int x, y, w; //起点,终点,权值;
for (int i = 1; i <= n; i++) {
cin >> x >> y >> w;
a[x][y] = w;
a[y][x] = w; //双向边,若有向图则不用加;
}
return 0;
}
链式前向星
点击查看代码
#include <iostream>
using namespace std;
int n;
struct sss{
int t, ne, w;
}e[100005];
int h[100005], cnt;
void add(int u, int v, int ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
e[cnt].w = ww;
h[u] = cnt;
}
int main() {
cin >> n;
int x, y, w; //起点,终点,权值;
for (int i = 1; i <= n; i++) {
cin >> x >> y >> w;
add(x, y, w);
add(y, x, w); //双向边,若有向图则不用加;
}
for (int i = 1; i <= n; i++) {
cout << i << "连接的点";
for (int j = h[i]; j; j = e[j].ne) {
cout << e[j].t << ' ';
}
cout << endl;
} //遍历;
return 0;
}
弗洛伊德(Floyd)
DP的思想,$ \Theta(n^3) $ 的做法;
多源最短路;
模板(邻接矩阵):
点击查看代码
#include <iostream>
#include <cstring>
using namespace std;
int n, m; //n个点,m条边;
int a[1005][1005];
int main() {
cin >> n >> m;
memset(a, 0x3f, sizeof(a)); //后面要找min,所以初始化很大;
int x, y, w;
for (int i = 1; i <= n; i++) a[i][i] = 0;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
a[x][y] = a[y][x] = w;
}
for (int k = 1; k <= n; k++) { //先枚举中间点;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << a[i][j] << ' ';
}
cout << endl;
}
return 0;
}
Floyd求最小环
点击查看代码
#include <iostream>
#include <cstring>
using namespace std;
int n, m; //n个点,m条边;
int a[1005][1005];
int dis[1005][1005];
int main() {
cin >> n >> m;
memset(dis, 0x3f, sizeof(dis)); //后面要找min,所以初始化很大;
int x, y, w;
for (int i = 1; i <= n; i++) a[i][i] = 0;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
a[x][y] = a[y][x] = w;
dis[x][y] = dis[y][x] = w;
}
int ans = 0xfffffffffff;
for (int k = 1; k <= n; k++) { //先枚举中间点;
for (int i = 1; i <= k - 1; i++) {
for (int j = i; j <= k - 1; j++) {
ans = min(ans, dis[i][j] + a[j][k] + a[k][i]);
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
cout << ans; //最小环长度;
return 0;
}
Dijstra
每次松弛一个点,一共松弛n次
朴素 $ O(n^2) $ 模板(邻接矩阵);
点击查看代码
#include <iostream>
#include <cstring>
using namespace std;
int n, m; //点数,边数;
int dis[100005];
bool vis[100005];
int a[1005][1005];
void Dijstra(int x) { //x为起点;
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[x] = 0;
int t = -1;
for (int i = 2; i <= n; i++) { //除去起点,只需松弛n - 1次;
for (int j = 1; j <= n; j++) {
if (!vis[x] && (t == -1 || dis[j] < dis[t])) t = j; //找到离源点最近的最优点;
}
vis[t] = true;
for (int j = 1; j <= n; j++) {
if (dis[j] < dis[t] + a[t][j]) {
dis[j] = dis[t] + a[t][j];
}
}
}
if (dis[m] == 0x3f3f3f3f) {
cout << -1;
} else cout << dis[m];
}
int main() {
cin >> n >> m;
int x, y, w;
for (int i = 1; i <= n; i++) {
cin >> x >> y >> w;
a[x][y] = w;
a[y][x] = w;
}
Dijstra(1);
return 0;
}
堆优化 $ \Theta(m log m) $
点击查看代码
#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;
int n, m, s; //点数,边数;
struct sss {
int t, ne, w;
}e[10000005];
int h[10000005], cnt;
void add(int u, int v, int ww) {
e[++cnt].w = ww;
e[cnt].ne = h[u];
e[cnt].t = v;
h[u] = cnt;
}
int dis[10000005];
bool vis[10000005];
typedef pair<int, int> P;
void Dijstra_dui_you_hua(int x) {
for (int i = 1; i <= n; i++) {
dis[i] = pow(2, 31) - 1;
}
memset(vis, 0, sizeof(vis));
dis[x] = 0;
priority_queue<P, vector<P>, greater<P> > q; //权值在前,序号在后,队列里存的是已经松驰过的;
q.push({0, x});
while(!q.empty()) {
int t = q.top().first;
int xu = q.top().second;
q.pop();
if (vis[xu]) continue;
vis[xu] = true;
for (int i = h[xu]; i; i = e[i].ne) {
int u = e[i].t;
if (dis[u] > dis[xu] + e[i].w) {
dis[u] = dis[xu] + e[i].w;
q.push({dis[u], u});
}
}
}
}
int main() {
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
cin >> n >> m >> s;
int x, y, w;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
add(x, y, w);
}
Dijstra_dui_you_hua(s);
for (int i = 1; i <= n; i++) {
cout << dis[i] << ' ';
}
return 0;
}
拓扑排序求最长路
应用范围:有向无环图;
点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n, m;
struct sss{
int t, ne;
long long w;
}e[500005];
int h[500005], cnt;
void add(int u, int v, long long ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
e[cnt].w = ww;
}
int d[500005];
long long dis[500005];
void topu(int s) {
for (int i = 1; i <= n; i++) dis[i] = -0x3f3f3f3f3f3f3f3f;
queue<int> q;
q.push(s);
dis[s] = 0;
d[s] = 0;
for (int i = 1; i <= n; i++) {
if (i == s) continue;
if (d[i] == 0) {
for (int j = h[i]; j; j = e[j].ne) {
int u = e[j].t;
d[u]--;
}
}
}
while(!q.empty()) {
int t = q.front();
q.pop();
for (int i = h[t]; i; i = e[i].ne) {
int u = e[i].t;
dis[u] = max(dis[u], dis[t] + e[i].w);
d[u]--;
if (d[u] == 0) q.push(u);
}
}
}
int main() {
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
int x, y;
long long w;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
add(x, y, w);
d[y]++;
}
topu(1);
if (dis[n] == -0x3f3f3f3f3f3f3f3f) cout << -1;
else cout << dis[n];
return 0;
}
Bellman_Ford 以及 SPFA
点击查看代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int n, m;
struct sss{
int t, ne, w;
}e[100005];
int h[100005], cnt;
void add(int u, int v, int ww) {
e[++cnt].t = v;
e[cnt].w = ww;
e[cnt].ne = h[u];
h[u] = cnt;
}
int dis[100005];
bool vis[100005];
void SPFA(int x) {
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[x] = 0;
vis[x] = true;
queue<int> q;
q.push(x);
while(!q.empty()) {
int t = q.front();
q.pop();
vis[t] = false; //出队就false;
for (int i = h[t]; i; i = e[i].ne) {
int u = e[i].t;
if (dis[u] > dis[t] + e[i].w) {
dis[u] = dis[t] + e[i].w;
if (!vis[u]) {
q.push(u);
vis[u] = true;
}
}
}
}
}
int backup[1005];
int Bellman_Ford(int x, int k, int m) { //k为到点n的最多经过的边数,m为总边数。正确性待检验;
memset(dis, 0x3f, sizeof(dis));
dis[x] = 0;
for (int i = 0; i < k; i++) {
memcpy(backup, dis, sizeof(dis)); //备份,防止串联。
for (int j = 1; j <= m; j++) {
int a = e[j].f, b = e[j].t, ww = e[j].w;
dis[b] = min(dis[b], backup[a] + e[j].w);
}
}
if (dis[n] < 0x3f3f3f3f / 2) {
return dis[n];
} else return -1;
}
int main() {
cin >> n >> m;
int x, y, w;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> w;
add(x, y, w);
add(y, x, w);
}
Bellman_Ford(1);
return 0;
}
SPFA判负环
因为SFPA的本质是Bellman-Ford的队列优化,其记录的是从源点到此点最多经过k条边的最短路,所以如果经过n-1次后还能更新,说明图中存在负环;
点击查看代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int f;
struct sss{
int t, w, ne;
}e[100005];
int h[100005], cnt;
void add(int u, int v, int ww) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
e[cnt].w = ww;
}
int dis[100005];
bool vis[100005];
int n, m, w;
int cn[100005]; //c[i]代表第i个点到源点所需边数;
bool SPFA(int x, int n) {
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(cn, 0, sizeof(cn));
queue<int> q;
for (int i = 1; i <= n; i++) {
q.push(i);
vis[i] = true;
}
while(!q.empty()) {
int t = q.front();
q.pop();
vis[t] = false;
for (int i = h[t]; i != 0; i = e[i].ne) {
int to = e[i].t;
if (dis[to] > dis[t] + e[i].w) {
dis[to] = dis[t] + e[i].w;
cn[to] = cn[t] + 1;
if (cn[to] >= n) return true; //满足条件,直接return;
if (!vis[to]) {
q.push(to);
vis[to] = true;
}
}
}
}
return false;
}
int main() {
cin >> f;
for (int i = 1; i <= f; i++) {
cin >> n >> m >> w;
memset(h, 0, sizeof(h));
for (int j = 1; j <= n; j++) {
e[j].ne = 0;
e[j].t = 0;
e[j].w = 0;
}
cnt = 0;
int u, v, ww;
for (int j = 1; j <= m; j++) {
cin >> u >> v >> ww;
add(u, v, ww);
add(v, u, ww);
}
for (int j = 1; j <= w; j++) {
cin >> u >> v >> ww;
add(u, v, -ww);
}
if (SPFA(1, n)) {
cout << "YES" << endl; //有负环;
} else {
cout << "NO" << endl; //无负环;
}
}
return 0;
}
<3> Tarjan
求强连通分量个数
点击查看代码
#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;
struct sss{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
e[cnt].t = v;
h[u] = cnt;
}
int dfn[1000005], low[1000005];
int num, su;
bool vis[1000005];
int belog[1000005];
stack<int> s;
int d[1000005];
int sum[1000005];
void tarjan(int x) {
dfn[x] = low[x] = ++num;
vis[x] = true;
s.push(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (!dfn[u]) {
tarjan(u);
low[x] = min(low[x], low[u]);
} else if (vis[u]) {
low[x] = min(low[x], dfn[u]);
}
}
if (dfn[x] == low[x]) {
su++;
int t = 0;
do {
t = s.top();
s.pop();
sum[su]++;
belog[t] = su;
vis[t] = false;
} while(t != x);
}
}
int n, m;
int main() {
scanf("%d %d", &n, &m);
int x, y;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
printf("%d", su);
return 0;
}
Tarjan缩点
以前者为例;
点击查看代码
#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
int n, m, st, p;
struct sss{
int t, ne;
}e[1000005], edge[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
}
int he[1000005], ccnt;
void add_2(int u, int v) {
edge[++ccnt].ne = he[u];
he[u] = ccnt;
edge[ccnt].t = v;
}
vector<int> vec;
bool v[1000005], vis[1000005];
int dfn[1000005], low[1000005];
int a[1000005];
int num, su;
int sum[1000005];
stack<int> s;
int start;
int belog[1000005];
void tarjan(int x) {
dfn[x] = low[x] = ++num;
vis[x] = true;
s.push(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (!dfn[u]) {
tarjan(u);
low[x] = min(low[x], low[u]);
} else if (vis[u]) {
low[x] = min(low[x], dfn[u]);
}
}
if (dfn[x] == low[x]) {
su++;
int t = 0;
do {
t = s.top();
s.pop();
belog[t] = su;
sum[su] += a[t];
vis[t] = false;
if (v[t]) vec.push_back(su);
if (t == st) start = su;
} while(t != x);
}
}
int dis[1000005], vvis[1000005];
void SPFA(int x) {
memset(vvis, 0, sizeof(vvis));
queue<int> q;
vvis[x] = true;
q.push(x);
while(!q.empty()) {
int t = q.front();
q.pop();
vvis[x] = false;
for (int i = he[t]; i; i = edge[i].ne) {
int u = edge[i].t;
if (dis[u] < dis[t] + sum[u]) { //不要吧u写成i!
dis[u] = dis[t] + sum[u];
if (!vvis[u]) {
q.push(u);
vvis[u] = true;
}
}
}
}
}
int main() {
scanf("%d %d", &n, &m);
int x, y;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
scanf("%d %d", &st, &p);
for (int i = 1; i <= p; i++) {
scanf("%d", &x);
v[x] = true;
}
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 = e[j].ne) {
int u = e[j].t;
if (belog[i] != belog[u]) {
add_2(belog[i], belog[u]);
}
}
}
for (int i = 1; i <= su; i++) dis[i] = sum[i];
SPFA(start);
int ma = -1;
for (int i = 0; i < vec.size(); i++) {
ma = max(ma, dis[vec[i]]);
}
printf("%d", ma);
return 0;
}
求割点
例题:备用交换机
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
using namespace std;
int n;
struct sss{
int t, ne;
}e[10000005];
int h[10000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
}
stack<int> s;
int dfn[1000005], low[1000005];
int d[1000005];
bool vis[1000005], cd[1000005];
int num;
int sum;
int root;
void tarjan(int x) {
dfn[x] = low[x] = ++num;
int son = 0;
s.push(x);
vis[x] = true;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (!dfn[u]) {
son++;
tarjan(u);
low[x] = min(low[x], low[u]);
if (low[u] >= dfn[x]) {
if (x != root || son > 1) cd[x] = true;
}
} else {
low[x] = min(low[x], dfn[u]);
}
}
}
int main() {
scanf("%d", &n);
int x, y;
while(scanf("%d %d", &x, &y) != EOF) {
add(x, y);
add(y, x);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
root = i;
tarjan(i);
}
}
for (int i = 1; i <= n; i++) {
if (cd[i]) sum++;
}
printf("%d\n", sum);
for (int i = 1; i <= n; i++) {
if (cd[i]) printf("%d\n", i);
}
return 0;
}
求割点与乘法原理的结合
例题:BLO;
点击查看代码
#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;
int n, m;
struct sss{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
}
int dfn[1000005], low[1000005];
int num;
stack<int> s;
bool vis[1000005];
long long ans[1000005];
long long sum[1000005];
bool cd[1000005];
void tarjan(int x) {
dfn[x] = low[x] = ++num;
vis[x] = true;
s.push(x);
int son = 0;
sum[x] = 1;
int su = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (!dfn[u]) {
son++;
tarjan(u);
sum[x] += sum[u];
low[x] = min(low[x], low[u]);
if (low[u] >= dfn[x]) {
su += sum[u];
ans[x] += sum[u] * (n - sum[u]);
if (x != 1 || son > 1) {
cd[x] = true;
}
}
} else if (vis[u]) {
low[x] = min(low[x], dfn[u]);
}
}
if (cd[x]) {
ans[x] += (long long)(n - su - 1) * (su + 1) + n - 1;
} else {
ans[x] = (n - 1) << 1;
}
if (dfn[x] == low[x]) {
int t = 0;
do {
t = s.top();
s.pop();
vis[t] = false;
} while(t != x);
}
}
int main() {
scanf("%d %d", &n, &m);
int x, y;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
tarjan(1);
for (int i = 1; i <= n; i++) {
printf("%lld\n", ans[i]);
}
return 0;
}
求割边
例题:旅游航道;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
using namespace std;
int n, m;
struct sss{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
}
int dfn[1000005], low[1000005];
int num;
stack<int> s;
int ans;
bool vis[1000005];
bool bri[1000005];
void tarjan(int x, int fa) {
dfn[x] = low[x] = ++num;
vis[x] = true;
s.push(x);
bool first = true;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa && first) {
first = false;
continue;
}
if (!dfn[u]) {
tarjan(u, x);
low[x] = min(low[x], low[u]);
if (low[u] > dfn[x]) {
ans++;
bri[i] = bri[i ^ 1] = true;
}
} else if (vis[u]) {
low[x] = min(low[x], dfn[u]);
}
}
if (dfn[x] == low[x]) {
int t = 0;
do {
t = s.top();
s.pop();
vis[t] = false;
} while(t != x);
}
}
int main() {
scanf("%d %d", &n, &m);
while(n != 0 && m != 0) {
int x, y;
memset(e, 0, sizeof(e));
memset(h, 0, sizeof(h));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(vis, 0, sizeof(vis));
memset(bri, 0, sizeof(bri));
num = 0;
cnt = 0;
ans = 0;
while(!s.empty()) s.pop();
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i, 0);
}
printf("%d\n", ans);
scanf("%d %d", &n, &m);
}
return 0;
}
求边双并重新建没有桥的图
点击查看代码
#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
using namespace std;
int n, m;
struct sss{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].ne = h[u];
h[u] = cnt;
e[cnt].t = v;
}
int dfn[1000005], low[1000005];
int num;
int d[1000005];
stack<int> s;
int su;
int belog[1000005];
vector<int> ed[1000005];
void tarjan(int x, int id) {
dfn[x] = low[x] = ++num;
s.push(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (i == (id ^ 1)) continue;
if (!dfn[u]) {
tarjan(u, i);
low[x] = min(low[x], low[u]);
} else {
low[x] = min(low[x], dfn[u]);
}
}
if (dfn[x] == low[x]) {
int t = 0;
su++;
do {
t = s.top();
s.pop();
belog[t] = su;
ed[t].push_back(t);
} while(t != x);
}
}
int main() {
scanf("%d %d", &n, &m);
int x, y;
cnt = 1;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
tarjan(1, 1);
for (int i = 1; i <= n; i++) {
for (int j = h[i]; j; j = e[j].ne) {
int u = e[j].t;
if (belog[i] != belog[u]) {
// d[belog[i]]++;
d[belog[u]]++; //无向边i和u强连通,所以只需加一次,且加哪个都行;
}
}
}
long long ans = 0;
for (int i = 1; i <= su; i++) {
if (d[i] == 1) ans++;
}
printf("%lld", (ans + 1) >> 1);
return 0;
}
求边双
P8436 【模板】边双连通分量 ](https://www.luogu.com.cn/problem/P8436 "Luogu P8436 【模板】边双连通分量 ")
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
int n, m;
struct sss{
int t, ne;
}e[5000005];
int h[5000005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
int tot;
bool vis[500005];
vector<int> v[500005];
int dfn[500005], low[500005], dcnt;
bool bri[5000005];
void Tarjan(int x, int id) {
dfn[x] = low[x] = ++dcnt;
for (int i = h[x]; i; i = e[i].ne) {
if (i == (id ^ 1)) continue;
int u = e[i].t;
if (!dfn[u]) {
Tarjan(u, i);
low[x] = min(low[x], low[u]);
if (low[u] > dfn[x]) {
bri[i] = bri[i ^ 1] = true;
}
} else {
low[x] = min(low[x], dfn[u]);
}
}
}
void dfs(int x, int now) {
vis[x] = true;
v[now].push_back(x);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (vis[u] || bri[i]) continue;
dfs(u, now);
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
int x, y;
cnt = 1;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) Tarjan(i, 1);
}
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) {
if (!vis[i]) dfs(i, ++tot);
}
cout << tot << '\n';
for (int i = 1; i <= tot; i++) {
cout << v[i].size() << ' ';
for (int j = 0; j < v[i].size(); j++) {
cout << v[i][j] << ' ';
}
cout << '\n';
}
return 0;
}
求点双
点击查看代码
#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
using namespace std;
int n, m;
struct sss{
int t, ne;
}e[5000005];
int h[5000005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
int dfn[500005], low[500005], dcnt;
bool vis[500005];
stack<int> s;
vector<int> v[500005];
int tot;
bool gd[500005];
void Tarjan(int x, int rt) {
dfn[x] = low[x] = ++dcnt;
s.push(x);
vis[x] = true;
bool vv = false;
int son = 0;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == x) continue;
vv = true;
if (!dfn[u]) {
son++;
Tarjan(u, rt);
low[x] = min(low[x], low[u]);
if (dfn[x] <= low[u]) {
if (x != rt || son > 1) gd[x] = true;
tot++;
int t = 0;
do {
t = s.top();
s.pop();
v[tot].push_back(t);
}while(t != u);
v[tot].push_back(x);
}
} else if (vis[u]) {
low[x] = min(low[x], dfn[u]);
}
}
if (!vv) v[++tot].push_back(x);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
int x, y;
for (int i = 1; i <= m; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) Tarjan(i, i);
}
cout << tot << '\n';
for (int i = 1; i <= tot; i++) {
cout << v[i].size() << ' ';
for (int j = 0; j < v[i].size(); j++) {
cout << v[i][j] << ' ';
}
cout << endl;
}
return 0;
}
例题:矿场搭建题解;
数据结构模板
树状数组 2024-02-18 星期日
概念类知识
所谓树状数组,即像树的数组,一般应用于求多次前缀和以及区间前缀和的问题中;
其根据节点编号的二进制数的末尾0的个数划分层次,每个节点的管辖范围为2^k,其中k为此节点的二进制数末尾0的个数,并用lowbit实现跳父亲的操作;
所谓lowbit,就是一个数的二进制的从右往左数第一个1出现的位置和这个1右面所有0构成的数,如6 == 110, 则lowbit(6) == 10 == 2; 6 + lowbit(6) == 8 == 1000,则节点8是节点6的父亲(父亲由其儿子数值加和转移而来,具体见下图);
一维树状数组
树状数组的主要题目有三类:
1.单点修改,区间查询(树状数组操作的基础,后面的2个操作都是由它转变而来);
对于单点修改,我们只需先修改它自己,然后一步步修改其父亲即可;
对于区间查询,根据上面的思路,我们可以知道,树状数组可以查询1i的区间和,如果要查询xy的区间和,只需用(1 ~ y) - (1 ~ x - 1)即可;
点击查看代码
#include <iostream>
#include <string>
using namespace std;
int n, m;
int a[1000005];
int t[1000005];
string s;
int lowbit(int x) {
return x & (-x);
}
void add_dian(int x, int k) { //对第x个数加k(单点修改);
while(x <= n) {
t[x] += k;
x += lowbit(x);
}
}
int ask_he(int l, int r) { //查询区间和(区间查询);
int ans = 0;
int i = l - 1;
while(i > 0) {
ans -= t[i];
i -= lowbit(i);
}
i = r;
while(i > 0) {
ans += t[i];
i -= lowbit(i);
}
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
add_dian(i, a[i]);
}
cin >> m;
int c, d;
for (int i = 1; i <= m; i++) {
cin >> s;
cin >> c >> d;
if (s == "ADD") {
add_dian(c, d);
}
if (s == "SUM") {
cout << ask_he(c, d) << endl;
}
}
return 0;
}
2.区间修改,单点查询;
对于区间修改,我们如果遍历修改的话会TLE,所以我们要维护原数组的差分数组b,每次修改区间(x, y)时只需修改x 和 y + 1的值即可(将b[x] + k, b[y + 1] - k);
对于单点查询i,我们只需求b的前缀和(1 ~ i)即可;
记得开long long!
点击查看代码
#include <iostream>
#include <string>
using namespace std;
int n, m;
string s;
int a[1000005];
int b[1000005]; //a的差分数组;
int t[1000005]; //维护b的树状数组;
int lowbit(int x) {
return x & (-x);
}
void add_dian(int x, int k) {
while(x <= n) {
t[x] += k;
x += lowbit(x);
}
}
//如要进行区间修改,只需将起点加k,终点 + 1减k(差分数组);
long long ask_dian(int x) { //输出点(a数组中,x为位置);
long long ans = 0;
while(x > 0) {
ans += t[x];
x -= lowbit(x);
}
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
b[i] = a[i] - a[i - 1];
}
for (int i = 1; i <= n; i++) {
add_dian(i, b[i]);
}
cin >> m;
int c, d, e;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == "ADD") {
cin >> c >> d >> e;
add_dian(c, e);
add_dian(d + 1, -e);
}
if (s == "QUERY") {
cin >> c;
cout << ask_dian(c) << endl;
}
}
return 0;
}
3.区间修改,区间查询;
这个比较麻烦,需要推公式,个人感觉最好用线段树做;
公式的推导:
设T(n) 为原数组的前缀和,S(n)为差分数组的前缀和;
则:
所以,我们只需维护两个树状数组(差分数组的和i*差分数组的)即可;
点击查看代码
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int n, m;
string s;
int a[100005];
int b[100005]; //a的差分数组;
long long t[100005]; //维护b的树状数组;
long long t1[100005]; //
int lowbit(int x) {
return x & (-x);
}
void add_dian(int x, long long k) {
long long val = 1ll * k * x; //(x + a[i]) * y 和 a[i] * y 差 x * y;
while(x <= n) {
t[x] += k;
t1[x] += val;
x += lowbit(x);
}
}
void add_dian1(int x, long long k) {
while(x <= n) {
t1[x] += k;
x += lowbit(x);
}
}
//如要进行区间修改,只需将起点加k,终点减k(差分数组);
long long ask_sum(int x) {
long long ans = 0;
while(x > 0) {
ans += t[x];
x -= lowbit(x);
}
return ans;
}
long long ask_sum1(int x) {
long long ans = 0;
while(x > 0) {
ans += t1[x];
x -= lowbit(x);
}
return ans;
}
long long ask(int l, int r) {
long long ans1 = ask_sum(r) * (r + 1) - ask_sum(l - 1) * l; //根据公式(x + 1) * ask_sum(x) - ask_sum1(x)得来(只不过前面的x + 1分开了)(以前是r 和 l - 1,因为公式(x + 1),所以是(r + 1) 和 l;
long long ans2 = ask_sum1(r) - ask_sum1(l - 1);
return ans1 - ans2;
}
int main() {
memset(t, 0, sizeof(t));
memset(t1, 0, sizeof(t1));
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
b[i] = a[i] - a[i - 1];
}
for (int i = 1; i <= n; i++) {
add_dian(i, b[i]);
}
cin >> m;
int c, d;
long long e;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == "ADD") {
cin >> c >> d >> e;
add_dian(c, e);
add_dian(d + 1, -e);
}
else {
cin >> c >> d;
cout << ask(c, d) << endl;
}
}
return 0;
}
最后,附上例题及题解,辅助理解;
二维树状数组
二维树状数组适用于矩阵,t[x][y]维护从点(1, 1)到点(x, y)的矩阵前缀和;
1.单点修改,区间查询;
前者很简单,只需将一维改为二维即可;
后者的意思是求点(x1, y1)到点(x2, y2)的和;
贴上图,辅助理解;
先从简单入手,如要求从点(1, 1)到点(x, y)的和,黄色和蓝色部分均已知(前面已经维护过了),则要求的红色部分由黄色 + 蓝色 - 绿色 + a[x][y]即可(a为原数组);
所以
同理,要求点(x1, y1) 到点(x2, y2)的和,只需将点(1, 1)等效替代为点(x1, y1)即可;
贴图:
如图,已知黄色部分,要求紫色部分,只需用黄色 - 两个蓝色 + 一个绿色即可得出答案;
所以要求的区间和即为
点击查看代码
#include <iostream>
using namespace std;
int k, a, b, c, d;
int n;
int lowbit(int x) {
return x & (-x);
}
int t[5005][5005];
void add_dian(int x, int y, int k) { //将点(x, y)增加k;
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= n; j += lowbit(j)) {
t[i][j] += k;
}
}
}
long long ask_he(int x, int y) { //查询从点(1, 1)到点(x, y)的和;
long long ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
ans += t[i][j];
}
}
return ans;
}
long long ask(int x1, int y1, int x2, int y2) { //查询从点(x1, y1)到点(x2, y2)的和;
return ask_he(x2 + 1, y2 + 1) - ask_he(x2 + 1, y1) - ask_he(x1, y2 + 1) + ask_he(x1, y1);
}
int main() {
cin >> k;
while(k != 3) {
if (k == 0) {
cin >> n;
} else if (k == 1) {
cin >> a >> b >> c;
add_dian(a + 1, b + 1, c);
} else if (k == 2) {
cin >> a >> b >> c >> d;
cout << ask(a, b, c, d) << endl;
}
cin >> k;
}
return 0;
}
2.区间修改,单点查询;
和一位一样,二维也需要维护一个差分数组;
思考我们维护差分数组的本质---求单点的值;
考虑a数组的前缀和t,由a求t的公式为
设a的差分数组为d,我们知道,a是d的前缀和,则易得
则
这样就可以维护二维差分d了;
对于区间修改,贴上图
点击查看代码
#include <iostream>
#include <cstring>
using namespace std;
int t1;
int n, m;
int t[1010][1010];
int lowbit(int x) {
return x & (-x);
}
void add_dian(int x, int y, int k) { //点(x, y) + k;
for (int i = x; i <= 1005; i += lowbit(i)) {
for (int j = y; j <= 1005; j += lowbit(j)) {
t[i][j] ^= k;
}
}
}
long long ask_sum(int x, int y) {
long long ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
ans ^= t[i][j];
}
}
return ans;
}
int main() {
cin >> t1;
while(t1--) {
cin >> n >> m;
memset(t, 0, sizeof(t));
char s;
int a, b, c, d;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == 'C') {
cin >> a >> b >> c >> d;
add_dian(a, b, 1);
add_dian(c + 1, d + 1, 1);
add_dian(c + 1, b, 1);
add_dian(a, d + 1, 1);
}
if (s == 'Q') {
cin >> a >> b;
cout << ask_sum(a, b) << endl;
}
}
cout << endl;
}
return 0;
}
3.区间修改,区间查询;
太麻烦,不想写了;
直接贴图;
图是从课件上截下来的,课件是的
文字都是自己写的;
线段树
线段树是一棵二叉搜索树,它支持上述树状数组的全部操作,并支持树状数组外的其它例如求区间最值等的操作,既支持在线操作,也支持离线操作;
线段树与树状数组对比:
优点:线段树适用范围广,当你正确写出板子时,其它操作就变得很简单;
缺点:板子代码量大,容易打错且不易察觉;
常数比树状数组大;
查错麻烦(由于使用递归形式建树及更新值);
线段树的每个点存储的是一段区间的信息,每个节点的编号是普通二叉树的编号;
所以,对于一个节点x,其左儿子的编号为x << 1;其右儿子的编号为(x << 1) + 1(或者写成更快的位运算x << 1 | 1,因为x << 1为偶数,x << 1 | 1就相当于(x << 1) + 1;
设节点x管辖区间为(l, r),则定义左儿子管辖区间为(l, mid),右儿子管辖区间为(mid + 1, r);
对于一棵二叉树,我们通常使用递归形式建树,先从根节点出发分别递归左右儿子,最后回溯时更新值;
线段树同理,我们使用递归形式建树,先从根节点出发分别递归左右儿子,最后回溯时更新值(一般为区间最值和区间和);
首先开一个结构体储存该点的区间左右端点,以及区间最值和区间和;
点击查看代码
struct sss{
int l, r, ma, sum; //左端点,右端点,区间最大值,区间和;
}tr[50000005]; //要开至少4倍空间,防炸;
建树部分代码:
点击查看代码
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
tr[id].ma = a[l];
tr[id].sum = a[l];
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
建完了树,开始操作;
1.单点修改,区间查询;
这个挺简单;
点击查看代码
#include <iostream>
#include <string>
using namespace std;
int n, m;
string s;
int a[100005];
struct sss{
int l, r, ma, sum; //左端点,右端点,区间最大值,区间和;
}tr[50000005]; //要开4倍空间;
int ls(int x) { //x的左孩子编号;
return x << 1;
}
int rs(int x) { //x的右孩子编号;
return x << 1 | 1;
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
tr[id].ma = a[l];
tr[id].sum = a[l];
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
void add_dian(int id, int x, int k) { //单点修改,将位置为x的数加上k(id为现在找到的点编号);
if (tr[id].l == tr[id].r) {
tr[id].ma = k;
tr[id].sum += k; //加k操作;
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
add_dian(x <= mid ? ls(id) : rs(id), x, k); //看看x在左子树还是右子树(注意中点在左子树);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum; //更新每个sum值;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
int ask_sum(int id, int l, int r) { //查询区间(l, r)的和(id为现在找的点的编号);
if (tr[id].l >= l && tr[id].r <= r) {
return tr[id].sum; //如果区间正好被包含就不用找了;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (r <= mid) return ask_sum(ls(id), l, r);
else if (l > mid) return ask_sum(rs(id), l, r);
else return ask_sum(ls(id), l, mid) + ask_sum(rs(id), mid + 1, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (n != 0) bt(1, 1, n);
cin >> m;
int k, d;
for (int i = 1; i <= m; i++) {
cin >> s;
cin >> k >> d;
if (s == "ADD") {
add_dian(1, k, d);
}
else if (s == "SUM") {
cout << ask_sum(1, k, d) << endl;
}
}
return 0;
}
2.区间修改,区间查询及单点查询;
对于区间修改,如果要全部修改的话,从该区间开始要一直修改到根,会TLE;
怎么办呢?
我们引入一个新的东西:懒惰标记;
所谓懒惰标记,即在更新时只更新到管辖此更新区间的子树的根,并将此根的懒惰标记 += 要更新的值,等查询时在下放标记更新它的子节点,这样就可以减少许多不必要的操作,进而避免TLE;
具体更新操作见下面的题;
对于后面的查询,单点查询可以理解成区间长度为1的查询,每次查询从mid递归寻找原区间,最后将答案合并即为最终答案;
点击查看代码
#include <iostream> //要开long long, long long, long long!!!!!!!!!!!!!!!!!!!;
#include <string>
using namespace std;
int n, m;
string s;
long long a[10000005];
struct sss{
int l, r;
long long ma, sum, lazy; //左端点,右端点,区间最大值,区间和,懒惰标记;long long, long long, long long!!!!!!!!!!!!!!!!!!!!!!!!!;
}tr[50000005]; //要开4倍空间;
int ls(int x) { //x的左孩子编号;
return x << 1;
}
int rs(int x) { //x的右孩子编号;
return x << 1 | 1;
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
tr[id].ma = a[l];
tr[id].sum = a[l];
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
void push_down(int id) { //下放标记;
tr[ls(id)].lazy += tr[id].lazy;
tr[rs(id)].lazy += tr[id].lazy;
tr[ls(id)].sum += tr[id].lazy * (tr[ls(id)].r - tr[ls(id)].l + 1);
tr[rs(id)].sum += tr[id].lazy * (tr[rs(id)].r - tr[rs(id)].l + 1); //sum存储区间总和;
tr[id].lazy = 0;
}
void add(int id, int a, int b, int l, int r, int k) { //区间修改,将区间(l, r)加k;
if (l > b || r < a) return;
if (a >= l && b <= r) {
tr[id].sum += k * (b - a + 1);
tr[id].lazy += k;
return;
}
int mid = (a + b) >> 1; //以修改区间为基准,找被修改区间(原区间);
push_down(id);
add(ls(id), a, mid, l, r, k);
add(rs(id), mid + 1, b, l, r, k);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
}
long long ask_he(int id, int a, int b, int l, int r) { //区间(l, r)查询和;
if (a > r || b < l) return 0;
if (a >= l && b <= r) return tr[id].sum;
int mid = (a + b) >> 1; //同上;
push_down(id);
return ask_he(ls(id), a, mid, l, r) + ask_he(rs(id), mid + 1, b, l, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (n != 0) bt(1, 1, n);
cin >> m;
int k, d, c;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == "ADD") {
cin >> k >> d >> c;
add(1, 1, n, k, d, c);
} else if (s == "QUERY") {
cin >> k;
cout << ask_he(1, 1, n, k, k) << endl;
}
}
return 0;
}
点击查看代码
#include <iostream> //要开long long, long long, long long!!!!!!!!!!!!!!!!!!!;
#include <string>
using namespace std;
int n, m;
string s;
long long a[10000005];
struct sss{
int l, r;
long long ma, sum, lazy; //左端点,右端点,区间最大值,区间和,懒惰标记;long long, long long, long long!!!!!!!!!!!!!!!!!!!!!!!!!;
}tr[50000005]; //要开4倍空间;
int ls(int x) { //x的左孩子编号;
return x << 1;
}
int rs(int x) { //x的右孩子编号;
return x << 1 | 1;
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
tr[id].ma = a[l];
tr[id].sum = a[l];
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
void push_down(int id) { //下放标记;
tr[ls(id)].lazy += tr[id].lazy;
tr[rs(id)].lazy += tr[id].lazy;
tr[ls(id)].sum += tr[id].lazy * (tr[ls(id)].r - tr[ls(id)].l + 1);
tr[rs(id)].sum += tr[id].lazy * (tr[rs(id)].r - tr[rs(id)].l + 1); //sum存储区间总和;
tr[id].lazy = 0;
}
void add(int id, int a, int b, int l, int r, int k) { //区间修改,将区间(l, r)加k;
if (l > b || r < a) return;
if (a >= l && b <= r) {
tr[id].sum += k * (b - a + 1);
tr[id].lazy += k;
return;
}
int mid = (a + b) >> 1; //以修改区间为基准,找被修改区间(原区间);
push_down(id);
add(ls(id), a, mid, l, r, k);
add(rs(id), mid + 1, b, l, r, k);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
}
long long ask_he(int id, int a, int b, int l, int r) { //区间(l, r)查询和;
if (a > r || b < l) return 0;
if (a >= l && b <= r) return tr[id].sum;
int mid = (a + b) >> 1; //同上;
push_down(id);
return ask_he(ls(id), a, mid, l, r) + ask_he(rs(id), mid + 1, b, l, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (n != 0) bt(1, 1, n);
cin >> m;
int k, d, c;
for (int i = 1; i <= m; i++) {
cin >> s;
cin >> k >> d;
if (s == "ADD") {
cin >> c;
add(1, 1, n, k, d, c);
} else if (s == "SUM") {
cout << ask_he(1, 1, n, k, d) << endl;
}
}
return 0;
}
总模板
点击查看代码
#include <iostream> //要开long long, long long, long long!!!!!!!!!!!!!!!!!!!;
#include <string>
using namespace std;
int n, m;
string s;
long long a[10000005];
struct sss{
int l, r;
long long ma, sum, lazy; //左端点,右端点,区间最大值,区间和,懒惰标记;long long, long long, long long!!!!!!!!!!!!!!!!!!!!!!!!!;
}tr[50000005]; //要开4倍空间;
int ls(int x) { //x的左孩子编号;
return x << 1;
}
int rs(int x) { //x的右孩子编号;
return x << 1 | 1;
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
tr[id].ma = a[l];
tr[id].sum = a[l];
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
void add_dian(int id, int x, int k) { //单点修改,将位置为x的数加上k(id为现在找到的点编号);
if (tr[id].l == tr[id].r) {
tr[id].ma = k;
tr[id].sum += k; //加k操作;
return;
}
int mid = (tr[id].l + tr[id].r) >> 1;
add_dian(x <= mid ? ls(id) : rs(id), x, k); //看看x在左子树还是右子树(注意中点在左子树);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum; //更新每个sum值;
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
long long ask_sum(int id, int l, int r) { //查询区间(l, r)的和(id为现在找的点的编号);
if (tr[id].l >= l && tr[id].r <= r) {
return tr[id].sum; //如果区间被包含就不用找了;
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (r <= mid) return ask_sum(ls(id), l, r);
else if (l > mid) return ask_sum(rs(id), l, r);
else return ask_sum(ls(id), l, mid) + ask_sum(rs(id), mid + 1, r);
}
void push_down(int id) { //下放标记;
tr[ls(id)].lazy += tr[id].lazy;
tr[rs(id)].lazy += tr[id].lazy;
tr[ls(id)].sum += tr[id].lazy * (tr[ls(id)].r - tr[ls(id)].l + 1);
tr[rs(id)].sum += tr[id].lazy * (tr[rs(id)].r - tr[rs(id)].l + 1); //sum存储区间总和;
tr[id].lazy = 0;
}
void add(int id, int a, int b, int l, int r, int k) { //区间修改,将区间(l, r)加k;
if (l > b || r < a) return;
if (a >= l && b <= r) {
tr[id].sum += k * (b - a + 1);
tr[id].lazy += k;
return;
}
int mid = (a + b) >> 1; //以修改区间为基准,找被修改区间(原区间);
push_down(id);
add(ls(id), a, mid, l, r, k);
add(rs(id), mid + 1, b, l, r, k);
tr[id].sum = tr[ls(id)].sum + tr[rs(id)].sum;
}
long long ask_he(int id, int a, int b, int l, int r) { //区间(l, r)查询和;
if (a > r || b < l) return 0;
if (a >= l && b <= r) return tr[id].sum;
int mid = (a + b) >> 1; //同上;
push_down(id);
return ask_he(ls(id), a, mid, l, r) + ask_he(rs(id), mid + 1, b, l, r);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (n != 0) bt(1, 1, n);
cin >> m;
int k, d, c;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == "ADD") {
cin >> k >> d >> c;
add(1, 1, n, k, d, c);
} else if (s == "QUERY") {
cin >> k;
cout << ask_he(1, 1, n, k, k) << endl;
}
}
return 0;
}
还有一篇写的不错的博客,可以参考参考(里面的图画的非常详细);、
线段树合并
Luogu P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并
看看板子理解一下,主要是复习用;
合并的过程就是暴力合并管辖相同范围的两棵树的两个节点,若两个节点中有一个没值,则返回另一个。若都有值,则继续向下递归直到叶子节点;
用的是动态开点线段树,时间复杂度约为单次 $ \Theta(\log n) $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, m;
int tot;
struct sss{
int t, ne;
}e[500005];
int h[500005], cnt;
int f[100005][35], dep[5000005], rt[5000005], sum[5000005], kind[5000005], ls[5000005], rs[5000005];
int ans[500005];
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
void dfs(int x, int fa) {
f[x][0] = fa;
dep[x] = dep[fa] + 1;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
dfs(u, x);
}
}
int lca(int x, int y) {
if (x == y) return x;
if (dep[x] < dep[y]) swap(x, y);
for (int i = 30; i >= 0; i--) {
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if (x == y) return x;
for (int i = 30; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
inline void push_up(int id) {
if (sum[ls[id]] < sum[rs[id]]) {
kind[id] = kind[rs[id]];
sum[id] = sum[rs[id]];
if (sum[id] == 0) kind[id] = 0;
} else {
kind[id] = kind[ls[id]];
sum[id] = sum[ls[id]];
if (sum[id] == 0) kind[id] = 0;
}
}
int add(int id, int l, int r, int col, int k) {
if (!id) id = ++cnt;
if (l == r) {
sum[id] += k;
kind[id] = col;
return id;
}
int mid = (l + r) >> 1;
if (col <= mid) {
ls[id] = add(ls[id], l, mid, col, k);
} else {
rs[id] = add(rs[id], mid + 1, r, col, k);
}
push_up(id);
return id;
}
int merge(int x, int y, int l, int r) {
if (!x || !y) return x + y;
if (l == r) {
sum[x] += sum[y];
return x;
}
int mid = (l + r) >> 1;
ls[x] = merge(ls[x], ls[y], l, mid);
rs[x] = merge(rs[x], rs[y], mid + 1, r);
push_up(x);
return x;
}
void ansfs(int x) {
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == f[x][0]) continue;
ansfs(u);
rt[x] = merge(rt[x], rt[u], 1, 100000);
}
ans[x] = kind[rt[x]];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
int x, y;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs(1, 0);
for (int j = 1; j <= 33; j++) {
for (int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j - 1]][j - 1];
}
}
int z;
for (int i = 1; i <= m; i++) {
cin >> x >> y >> z;
rt[x] = add(rt[x], 1, 100000, z, 1);
rt[y] = add(rt[y], 1, 100000, z, 1);
int lc = lca(x, y);
rt[lc] = add(rt[lc], 1, 100000, z, -1);
rt[f[lc][0]] = add(rt[f[lc][0]], 1, 100000, z, -1);
}
ansfs(1);
for (int i = 1; i <= n; i++) cout << ans[i] << endl;
return 0;
}
线段树分裂
分裂其实就是合并的逆过程,每次递归时把在需要分裂的区间的节点搞出来即可;
不要忘了最后要 $ push \ up $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, m;
int a[5000005];
int rt[5000005], ls[5000005], rs[5000005];
long long sum[5000005];
int idx, cnt, tot;
int cur[5000005];
inline void push_up(int id) {
sum[id] = sum[ls[id]] + sum[rs[id]];
}
inline int neww() {
return cnt ? cur[cnt--] : ++tot; //内存回收;
}
inline void del(int &id) {
ls[id] = rs[id] = sum[id] = 0;
cur[++cnt] = id;
id = 0;
}
void bt(int &id, int l, int r) {
if (!id) id = neww();
if (l == r) {
sum[id] = a[l];
return;
}
int mid = (l + r) >> 1;
bt(ls[id], l, mid);
bt(rs[id], mid + 1, r);
push_up(id);
}
void spilt(int &x, int &y, int l, int r, int L, int R) {
if (r < L || l > R) return;
if (!x) return;
if (l >= L && r <= R) {
y = x;
x = 0;
return;
}
if (!y) {
y = neww();
}
int mid = (l + r) >> 1;
spilt(ls[x], ls[y], l, mid, L, R);
spilt(rs[x], rs[y], mid + 1, r, L, R);
push_up(x);
push_up(y);
}
int merge(int x, int y, int l, int r) {
if (!x || !y) return x + y;
if (l == r) {
sum[x] += sum[y];
del(y);
return x;
}
int mid = (l + r) >> 1;
ls[x] = merge(ls[x], ls[y], l, mid);
rs[x] = merge(rs[x], rs[y], mid + 1, r);
push_up(x);
del(y);
return x;
}
long long ask(int id, int l, int r, int L, int R) {
if (!id) return 0;
if (l >= L && r <= R) {
return sum[id];
}
int mid = (l + r) >> 1;
if (R <= mid) return ask(ls[id], l, mid, L, R);
else if (L > mid) return ask(rs[id], mid + 1, r, L, R);
else return ask(ls[id], l, mid, L, mid) + ask(rs[id], mid + 1, r, mid + 1, R);
}
void add(int &id, int l, int r, int pos, int d) {
if (!id) id = neww();
if (l == r) {
sum[id] += d;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) add(ls[id], l, mid, pos, d);
else add(rs[id], mid + 1, r, pos, d);
push_up(id);
}
int ask_kth(int id, int l, int r, int k) {
if (l == r) return l;
int mid = (l + r) >> 1;
if (sum[ls[id]] >= k) {
return ask_kth(ls[id], l, mid, k);
} else {
return ask_kth(rs[id], mid + 1, r, k - sum[ls[id]]);
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
idx = 1;
bt(rt[1], 1, n);
int s, p, x, y;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == 0) {
cin >> p >> x >> y;
spilt(rt[p], rt[++idx], 1, n, x, y);
}
if (s == 1) {
cin >> p >> x;
rt[p] = merge(rt[p], rt[x], 1, n);
}
if (s == 2) {
cin >> p >> x >> y;
add(rt[p], 1, n, y, x);
}
if (s == 3) {
cin >> p >> x >> y;
cout << ask(rt[p], 1, n, x, y) << endl;
}
if (s == 4) {
cin >> p >> x;
if (sum[rt[p]] < x) {
cout << -1 << endl;
} else {
cout << ask_kth(rt[p], 1, n, x) << endl;
}
}
}
return 0;
}
线段树这东西很难调,一定一定要自己静下心来调;
李超线段树
维护在平面直角坐标系中若干条直线(或线段)与一条平行于 $ y $ 轴的直线的若干个交点的纵坐标的最大值;
显然,李超线段树可以应用于维护斜率优化;
好像也可以与树剖结合解决一些问题;
例题: Luogu P4097 【模板】李超线段树 / [HEOI2013] Segment
点击查看代码
#include <iostream>
using namespace std;
const double eps = 1e-9;
int n;
int ss;
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
double k, b;
}tr[10000005];
int s[10000005]; //维护以前的线段;
int cnt;
int cmp(double x, double y) {
if (x - y > eps) return 1;
else if (y - x > eps) return -1;
else return 0;
}
double calc(int id, int d) {
return tr[id].b + tr[id].k * d;
}
void add(int x0, int x1, int y0, int y1) { //新加一条线段;
cnt++;
if (x0 == x1) {
tr[cnt].k = 0;
tr[cnt].b = max(y0, y1);
} else {
tr[cnt].k = 1.0 * (y1 - y0) / (x1 - x0);
tr[cnt].b = y0 - tr[cnt].k * x0;
}
}
void add_line(int id, int zl, int zr, int u) { //在所找到的区间内判断最优;
int &v = s[id];
int mid = (zl + zr) >> 1;
int bm = cmp(calc(u, mid), calc(v, mid));
if (bm == 1 || (!bm && u < v)) swap(u, v);
int bl = cmp(calc(u, zl), calc(v, zl));
int br = cmp(calc(u, zr), calc(v, zr));
if (bl == 1 || (!bl && u < v)) {
add_line(ls(id), zl, mid, u);
}
if (br == 1 || (!br && u < v)) {
add_line(rs(id), mid + 1, zr, u);
}
}
void pos(int id, int zl, int zr, int l, int r, int u) { //确定直线所在值域;
if (l <= zl && zr <= r) {
add_line(id, zl, zr, u);
return;
}
int mid = (zl + zr) >> 1;
if (l <= mid) pos(ls(id), zl, mid, l, r, u);
if (r > mid) pos(rs(id), mid + 1, zr, l, r, u);
}
pair<double, int> pm(pair<double, int> x, pair<double, int> y) {
if (cmp(x.first, y.first) == -1) {
return y;
} else if (cmp(x.first, y.first) == 1) {
return x;
} else {
return x.second < y.second ? x : y;
}
}
pair<double, int> ask(int id, int zl, int zr, int d) {
if (zl > d || zr < d) return {0, 0};
int mid = (zl + zr) >> 1;
double val = calc(s[id], d);
if (zl == zr) return {val, s[id]};
return pm({val, s[id]}, pm(ask(ls(id), zl, mid, d), ask(rs(id), mid + 1, zr, d)));
}
int main() {
cin >> n;
int ans = 0;
int k;
int x0, y0, x1, y1;
for (int i = 1; i <= n; i++) {
cin >> ss;
if (ss == 0) {
cin >> k;
int x = (k + ans - 1 + 39989) % 39989 + 1;
ans = ask(1, 1, 39989, x).second;
cout << ans << endl;
}
if (ss == 1) {
cin >> x0 >> y0 >> x1 >> y1;
x0 = (x0 + ans - 1 + 39989) % 39989 + 1;
x1 = (x1 + ans - 1 + 39989) % 39989 + 1;
y0 = (y0 + ans - 1 + 1000000000) % 1000000000 + 1;
y1 = (y1 + ans - 1 + 1000000000) % 1000000000 + 1;
if (x0 > x1) swap(x0, x1), swap(y0, y1);
add(x0, x1, y0, y1);
pos(1, 1, 39989, x0, x1, cnt); //在值域上建立线段树;
}
}
return 0;
}
下面这个模板比较好,但不是这道题的,求的是一次函数的最大和最小值;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n, m;
long long a[1000005], b[1000005], suma[1000005], sumb[1000005];
long long ansl[1000005], ansr[1000005];
struct sss{
long long p, k;
int id;
inline bool operator <(const sss &A) const {
return p < A.p;
}
}q[1000005];
inline bool cmp(sss x, sss y) {
return x.p > y.p;
}
inline bool cmpx(sss x, sss y) {
return x.id < y.id;
}
namespace LCSEG{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r;
long long k, b;
}tr[8000005];
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
tr[id].k = 1e18;
tr[id].b = 1e18;
if (l == r) return;
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(int s, int id, long long k, long long b) {
if (tr[id].k == 1e18) {
tr[id].k = k;
tr[id].b = b;
if (tr[id].l == tr[id].r) return;
add(s, ls(id), k, b);
add(s, rs(id), k, b);
}
int mid = (tr[id].l + tr[id].r) >> 1;
if (s == 0) {
long long mtr = tr[id].k * mid + tr[id].b;
long long mli = k * mid + b;
if (mtr < mli) {
swap(tr[id].k, k);
swap(tr[id].b, b);
}
long long ltr = tr[id].k * tr[id].l + tr[id].b;
long long lli = k * tr[id].l + b;
long long rtr = tr[id].k * tr[id].r + tr[id].b;
long long rli = k * tr[id].r + b;
if (ltr < lli) add(s, ls(id), k, b);
else if (rtr < rli) add(s, rs(id), k, b);
} else if (s == 1) {
long long mtr = tr[id].k * mid + tr[id].b;
long long mli = k * mid + b;
if (mtr > mli) {
swap(tr[id].k, k);
swap(tr[id].b, b);
}
long long ltr = tr[id].k * tr[id].l + tr[id].b;
long long lli = k * tr[id].l + b;
long long rtr = tr[id].k * tr[id].r + tr[id].b;
long long rli = k * tr[id].r + b;
if (ltr > lli) add(s, ls(id), k, b);
else if (rtr > rli) add(s, rs(id), k, b);
}
}
long long ask(int s, int id, long long x) {
if (tr[id].l == tr[id].r) return tr[id].k * x + tr[id].b;
int mid = (tr[id].l + tr[id].r) >> 1;
long long val = tr[id].k * x + tr[id].b;
if (s == 0) {
if (x <= mid) return max(val, ask(s, ls(id), x));
else return max(val, ask(s, rs(id), x));
} else {
if (x <= mid) return min(val, ask(s, ls(id), x));
else return min(val, ask(s, rs(id), x));
}
}
}
using namespace LCSEG;
int main() {
freopen("seq.in", "r", stdin);
freopen("seq.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
suma[i] = suma[i - 1] + a[i];
sumb[i] = sumb[i - 1] + b[i];
}
bt(1, -1000000, 1000000);
for (int i = 1; i <= m; i++) {
cin >> q[i].p >> q[i].k;
q[i].id = i;
}
sort(q + 1, q + 1 + m);
int ls = 0;
for (int i = 1; i <= m; i++) {
for (int j = ls; j < q[i].p; j++) {
add(1, 1, -sumb[j], suma[j]);
}
ansl[q[i].id] = ask(1, 1, q[i].k) * (-1);
ls = q[i].p;
}
sort(q + 1, q + 1 + m, cmp);
ls = n - 1;
bt(1, -1000000, 1000000);
add(0, 1, -sumb[n], suma[n]);
for (int i = 1; i <= m; i++) {
for (int j = ls; j > q[i].p; j--) {
add(0, 1, -sumb[j], suma[j]);
}
ansr[q[i].id] = ask(0, 1, q[i].k);
ls = q[i].p;
}
sort(q + 1, q + 1 + m, cmpx);
for (int i = 1; i <= m; i++) {
cout << max(ansl[i] + ansr[i], suma[q[i].p] - q[i].k * sumb[q[i].p] + ansl[i]) << '\n';
}
return 0;
}
树链剖分
重链剖分
找重链,跳重链;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, m, r, p;
int a[1000005];
int fa[1000005], dfn[1000005], nfd[1000005], hson[1000005], htop[1000005], siz[1000005], dep[1000005], dcnt;
struct sss{
int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
}
void dfs1(int x, int fat) {
fa[x] = fat;
dep[x] = dep[fat] + 1;
siz[x] = 1;
hson[x] = -1;
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fat) continue;
dfs1(u, x);
siz[x] += siz[u];
if (hson[x] == -1 || siz[hson[x]] < siz[u]) hson[x] = u;
}
}
void dfs2(int x, int t) {
htop[x] = t;
dcnt++;
dfn[x] = dcnt;
nfd[dcnt] = x;
if (hson[x] == -1) return;
dfs2(hson[x], t);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u != fa[x] && u != hson[x]) dfs2(u, u);
}
}
namespace seg{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r, sum, lz;
}tr[9000005];
inline void push_up(int id) {
tr[id].sum = (tr[ls(id)].sum + tr[rs(id)].sum) % p;
}
inline void push_down(int id) {
if (tr[id].lz) {
tr[ls(id)].lz = (tr[ls(id)].lz + tr[id].lz) % p;
tr[rs(id)].lz = (tr[id].lz + tr[rs(id)].lz) % p;
tr[ls(id)].sum = (tr[ls(id)].sum % p + tr[id].lz * (tr[ls(id)].r - tr[ls(id)].l + 1) % p) % p;
tr[rs(id)].sum = (tr[rs(id)].sum % p + tr[id].lz * (tr[rs(id)].r - tr[rs(id)].l + 1) % p) % p;
tr[id].lz = 0;
}
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) {
tr[id].sum = a[nfd[l]] % p;
return;
}
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
push_up(id);
}
void add(int id, int l, int r, int d) {
if (tr[id].l >= l && tr[id].r <= r) {
tr[id].sum = (tr[id].sum % p + d * (tr[id].r - tr[id].l + 1) % p) % p;
tr[id].lz = (tr[id].lz + d) % p;
return;
}
push_down(id);
int mid = (tr[id].l + tr[id].r) >> 1;
if (l <= mid) add(ls(id), l, r, d);
if (r > mid) add(rs(id), l, r, d);
push_up(id);
}
int ask(int id, int l, int r) {
if (tr[id].l >= l && tr[id].r <= r) {
return tr[id].sum % p;
}
push_down(id);
int mid = (tr[id].l + tr[id].r) >> 1;
if (r <= mid) return ask(ls(id), l, r);
else if (l > mid) return ask(rs(id), l, r);
else return (ask(ls(id), l, mid) + ask(rs(id), mid + 1, r)) % p;
}
}
namespace tp{
void add(int x, int y, int d) {
while(htop[x] != htop[y]) {
if (dep[htop[x]] < dep[htop[y]]) swap(x, y);
seg::add(1, dfn[htop[x]], dfn[x], d);
x = fa[htop[x]];
}
if (dep[x] > dep[y]) swap(x, y);
seg::add(1, dfn[x], dfn[y], d);
}
int ask(int x, int y) {
int ans = 0;
while(htop[x] != htop[y]) {
if (dep[htop[x]] < dep[htop[y]]) swap(x, y);
ans = (ans % p + seg::ask(1, dfn[htop[x]], dfn[x]) % p) % p;
x = fa[htop[x]];
}
if (dep[x] > dep[y]) swap(x, y);
ans = (ans % p + seg::ask(1, dfn[x], dfn[y]) % p) % p;
return ans;
}
}
int main() {
cin >> n >> m >> r >> p;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int xx, yy;
for (int i = 1; i <= n - 1; i++) {
cin >> xx >> yy;
add(xx, yy);
add(yy, xx);
}
dep[r] = 1;
dfs1(r, 0);
dfs2(r, r);
seg::bt(1, 1, dcnt);
int s;
int x, y, z;
for (int i = 1; i <= m; i++) {
cin >> s;
if (s == 1) {
cin >> x >> y >> z;
tp::add(x, y, z);
}
if (s == 2) {
cin >> x >> y;
cout << tp::ask(x, y) % p << endl;
}
if (s == 3) {
cin >> x >> y;
seg::add(1, dfn[x], dfn[x] + siz[x] - 1, y);
}
if (s == 4) {
cin >> x;
cout << seg::ask(1, dfn[x], dfn[x] + siz[x] - 1) % p << endl;
}
}
return 0;
}
平衡树
Splay
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int rt, tot;
int n;
struct sss{
int fa, ls, rs, val, cnt, sz;
}tr[10000005];
inline int ls(int x) {
return tr[x].ls;
}
inline int rs(int x) {
return tr[x].rs;
}
inline int fa(int x) {
return tr[x].fa;
}
inline void mt(int id) {
tr[id].sz = tr[ls(id)].sz + tr[rs(id)].sz + tr[id].cnt;
}
inline bool rson(int id) {
return id == tr[fa(id)].rs;
}
inline void clear(int id) {
tr[id].fa = tr[id].ls = tr[id].rs = tr[id].val = tr[id].cnt = tr[id].sz = 0;
}
void rotate(int id) {
int y = tr[id].fa;
int z = tr[fa(id)].fa;
bool r = rson(id);
if (r) {
tr[y].rs = tr[id].ls;
} else {
tr[y].ls = tr[id].rs;
}
if (r) {
if (tr[id].ls) {
tr[ls(id)].fa = y;
}
} else {
if (tr[id].rs) {
tr[rs(id)].fa = y;
}
}
if (r) {
tr[id].ls = y;
} else {
tr[id].rs = y;
}
tr[y].fa = id;
tr[id].fa = z;
if (z) {
bool ry = (tr[z].rs == y);
if (ry) {
tr[z].rs = id;
} else {
tr[z].ls = id;
}
}
mt(y);
mt(id);
}
void splay(int id, int goal) {
if (goal == 0) rt = id;
while(tr[id].fa != goal) {
int f = tr[id].fa;
int g = tr[fa(id)].fa;
if (g != goal) {
if (rson(f) == rson(id)) {
rotate(f);
} else {
rotate(id);
}
}
rotate(id);
}
}
void add(int k) {
if (!rt) {
tot++;
tr[tot].val = k;
tr[tot].cnt++;
rt = tot;
mt(rt);
return;
}
int now = rt;
int f = 0;
while(1) {
if (tr[now].val == k) {
tr[now].cnt++;
mt(now);
mt(f);
splay(now, 0);
break;
}
f = now;
if (tr[now].val < k) {
now = tr[now].rs;
} else {
now = tr[now].ls;
}
if (!now) {
tot++;
tr[tot].val = k;
tr[tot].cnt++;
tr[tot].fa = f;
if (tr[f].val < k) {
tr[f].rs = tot;
} else {
tr[f].ls = tot;
}
mt(tot);
mt(f);
splay(tot, 0);
break;
}
}
}
int rk(int k) {
int ans = 0;
int now = rt;
while(1) {
if (k < tr[now].val) {
now = tr[now].ls;
} else {
ans += tr[ls(now)].sz;
if (!now) {
return ans + 1;
}
if (k == tr[now].val) {
splay(now, 0);
return ans + 1;
}
ans += tr[now].cnt;
now = tr[now].rs;
}
}
}
int ask_kth(int k) {
int now = rt;
while(1) {
if (tr[now].ls && k <= tr[ls(now)].sz) {
now = tr[now].ls;
} else {
k -= (tr[ls(now)].sz + tr[now].cnt);
if (k <= 0) {
splay(now, 0);
return tr[now].val;
}
now = tr[now].rs;
}
}
}
int ask_pre() {
int now = tr[rt].ls;
if (!now) return now;
while(tr[now].rs) now = tr[now].rs;
splay(now, 0);
return now;
}
int ask_nxt() {
int now = tr[rt].rs;
if (!now) return now;
while(tr[now].ls) now = tr[now].ls;
splay(now, 0);
return now;
}
void del(int k) {
rk(k);
if (tr[rt].cnt > 1) {
tr[rt].cnt--;
mt(rt);
return;
}
if (!tr[rt].ls && !tr[rt].rs) {
clear(rt);
rt = 0;
return;
}
if (!tr[rt].ls) {
int now = rt;
rt = tr[rt].rs;
tr[rt].fa = 0;
clear(now);
return;
}
if (!tr[rt].rs) {
int now = rt;
rt = tr[rt].ls;
tr[rt].fa = 0;
clear(now);
return;
}
int now = rt;
int x = ask_pre();
tr[rs(now)].fa = x;
tr[x].rs = tr[now].rs;
clear(now);
mt(rt);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
rt = 0;
tot = 0;
cin >> n;
int s;
int x;
for (int i = 1; i <= n; i++) {
cin >> s >> x;
if (s == 1) {
add(x);
}
if (s == 2) {
del(x);
}
if (s == 3) {
cout << rk(x) << endl;
}
if (s == 4) {
cout << ask_kth(x) << endl;
}
if (s == 5) {
add(x);
cout << tr[ask_pre()].val << endl;
del(x);
}
if (s == 6) {
add(x);
cout << tr[ask_nxt()].val << endl;
del(x);
}
}
return 0;
}
FHQ Treap
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
int n;
int rt, tot;
struct sss{
int ls, rs, val, dat, sz;
}tr[10000005];
inline int ls(int x) {
return tr[x].ls;
}
inline int rs(int x) {
return tr[x].rs;
}
inline void mt(int x) {
tr[x].sz = tr[ls(x)].sz + tr[rs(x)].sz + 1;
}
int neww(int k) {
tot++;
tr[tot].val = k;
tr[tot].dat = rand();
tr[tot].sz = 1;
return tot;
}
void split(int id, int k, int &x, int &y) {
if (!id) {
x = 0;
y = 0;
return;
}
if (tr[id].val <= k) {
x = id;
split(tr[id].rs, k, tr[id].rs, y);
} else {
y = id;
split(tr[id].ls, k, x, tr[id].ls);
}
mt(id);
}
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].dat < tr[y].dat) {
tr[x].rs = merge(tr[x].rs, y);
mt(x);
return x;
} else {
tr[y].ls = merge(x, tr[y].ls);
mt(y);
return y;
}
}
int ask_kth(int now, int k) {
if (!now) {
return 0;
}
while(1) {
if (k <= tr[ls(now)].sz) {
now = tr[now].ls;
} else {
if (k == tr[ls(now)].sz + 1) {
return now;
} else {
k -= (tr[ls(now)].sz + 1);
now = tr[now].rs;
}
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
#endif
srand(time(0));
cin >> n;
int s, x;
for (int i = 1; i <= n; i++) {
cin >> s >> x;
if (s == 1) {
int r1 = 0, r2 = 0;
split(rt, x, r1, r2);
rt = merge(merge(r1, neww(x)), r2);
}
if (s == 2) {
int r1 = 0, r2 = 0, r3 = 0;
split(rt, x, r1, r2);
split(r1, x - 1, r1, r3);
r3 = merge(tr[r3].ls, tr[r3].rs);
rt = merge(merge(r1, r3), r2);
}
if (s == 3) {
int r1 = 0, r2 = 0;
split(rt, x - 1, r1, r2);
cout << tr[r1].sz + 1 << endl;
rt = merge(r1, r2);
}
if (s == 4) {
cout << tr[ask_kth(rt, x)].val << endl;
}
if (s == 5) {
int r1 = 0, r2 = 0;
split(rt, x - 1, r1, r2);
cout << tr[ask_kth(r1, tr[r1].sz)].val << endl;
rt = merge(r1, r2);
}
if (s == 6) {
int r1 = 0, r2 = 0;
split(rt, x, r1, r2);
cout << tr[ask_kth(r2, 1)].val << endl;
rt = merge(r1, r2);
}
}
return 0;
}
ST表
$ \Theta(1) $ 查询 $ RMQ $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n, m;
int a[1000005];
int f[500005][35];
int lg[1000005];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
n = read();
m = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
lg[1] = 0;
for (int i = 2; i <= n; i++) {
lg[i] = lg[i >> 1] + 1;
}
int l;
int r;
for (int i = 1; i <= n; i++) {
f[i][0] = a[i];
}
for (int j = 1; j <= lg[n]; j++) {
for (int i = 1; i <= n - (1 << j) + 1; i++) {
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
for (int i = 1; i <= m; i++) {
l = read();
r = read();
int k = lg[r - l + 1];
cout << max(f[l][k], f[r - (1 << k) + 1][k]) << '\n';
}
return 0;
}
字符串
扩展欧几里得
求 $ ax + by = gcd(a, b) $ 的一组整数解 $ x, y $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
y = 0;
x = 1;
return a;
}
int res = exgcd(b, a % b, x, y);
int o = x;
x = y;
y = o - a / b * y;
return res;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
对于通解,可以用如下式子:$ x = x_0 + \frac{b}{gcd(a, b)} \times t, \ \ y = y_0 - \frac{a}{gcd(a, b)} \times t $;
其中 $ t $ 为任意整数;
卡特兰数
有数列 1 1 2 5 14 42 132...
,则其大概率满足卡特兰数;
通式:$ H(n) = \frac{C_{2n}^{n}}{n + 1} (n \geq 2) $;
欧拉路,欧拉回路(一笔画)
一个无向图有欧拉路,当其有两个奇点时(度为奇数的点),如果在有向图中,满足其他点的入度等于出度,但有一个点入度比出度小一,有另一个大一;
有欧拉回路,在无向图中当其无奇点时,在有向图中当所有点入度等于出度时;
所谓一笔画问题,即从某一个点走,不重复的经过每条边一次到一个点为止,如果回到原点,则这个路径叫欧拉回路;
找路径的话直接 dfs
即可,复杂度 $ \Theta(m) $,其中 $ m $ 为边数;
矩阵乘法
对于两个矩阵,它们两个可以相乘当前一个矩阵的行数等于后一个矩阵的列数时;
也就是说,一个 $ m \times p $ 和一个 $ n \times m $ 的矩阵可以相乘,最后得到一个 $ n \times p $ 的矩阵;
设前一个矩阵是 $ a $,后一个是 $ b $,设答案矩阵为 $ ans $,则 $ ans_{i, j} = \sum_{k = 1}^{m} a_{i, k} \times b_{k, j} $;
矩阵快速幂
对于一些线性递推算法(比如DP),状态转移方程一般是固定的,但当需要递推的次数很多时是不可接受的,这种情况下可以使用矩阵快速幂加速递推;
比如对于斐波那契数列 $ f_n = f_{n - 1} + f_{n - 2} $,求 $ f_{1e9} \mod p $;
当然可以用通项公式做,但如果要递推这么办呢?
发现要递推的项数很多,于是考虑转化为矩阵;
首先考虑转移到 $ f_n $,需要什么,需要 $ f_{n - 1}, f_{n - 2} $;
所以最终的矩阵中有 $ f_n $,初始矩阵中有 $ f_{n - 1}, f_{n - 2} $;
那么我们想要一个递推矩阵(这里的递推矩阵需要是正方形的,不然快速幂的时候形态会变),使得初始矩阵乘递推矩阵等于最终矩阵,然后对递推矩阵做快速幂即可解决这个问题;
考虑递推矩阵的构造,那么设其为 $ A $,那么我们发现, $ A_{1, 1} \times f_{n - 1} + A_{1, 2} \times f_{n - 2} = f_{n} \rightarrow A_{1, 1} = A_{1, 2} = 1 $;
然后最终矩阵还剩下一项,因为要继续递推,所以这一项是 $ f_{n - 1} $;
最后直接快速幂,复杂度 $ \log 1e9 $;
对于快速幂,其实和普通的无异,只不过是重载了乘法运算符;
那么对于这道题的矩阵转移为:
在实际情况中,我们要依据状态转移方程去适宜的选择我们矩阵的大小,一般从最终矩阵和初始矩阵入手,找递推矩阵;
To be continued...