1.基础算法
1.线性筛模板
int st[N], primes[N], cnt;
void get_primes(int n)
{for(int i = 2; i <= n; i++){if(!st[i]) primes[cnt++] = i;for(int j = 0; primes[j] <= n / i; j ++){st[primes[j]*i] = true;if(i % primes[j] == 0) break;}}
}
2.二/三分模板
二分
版本1
当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。(右边满足的最小值)
C++ 代码模板:
int bsearch_1(int l, int r)
{while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid;else l = mid + 1;}return l;
}
版本2
当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。(左边满足的最大值)
C++ 代码模板:
int bsearch_2(int l, int r)
{while (l < r){int mid = l + r + 1 >> 1;if (check(mid)) l = mid;else r = mid - 1;}return l;
}
三分
求凸函数和凹函数的最值
版本1 -> 实数三分
const double EPS = 1e-9;
while(r - l < EPS) {double lmid = l + (r - l) / 3;double rmid = r - (r - l) / 3;lans = cal(lmid),rans = cal(rmid);// 求凹函数的极小值if(lans <= rans) r = rmid;else l = lmid;// 求凸函数的极大值if(lans >= rans) l = lmid;else r = rmid;
}
// 输出 l 或 r 都可
cout << l << endl;
版本2 -> 整数三分
int l = 1,r = 100;
while(l < r) {int lmid = l + (r - l) / 3; // l + 1/3区间大小int rmid = r - (r - l) / 3; // r - 1/3区间大小lans = cal(lmid),rans = cal(rmid);// 求凹函数的极小值if(lans <= rans) r = rmid - 1;else l = lmid + 1;// 求凸函数的极大值if(lasn >= rans) l = lmid + 1;else r = rmid - 1;
}
// 求凹函数的极小值
cout << min(lans,rans) << endl;
// 求凸函数的极大值
cout << max(lans,rans) << endl;
3.试除法求所有约数
vector<int> get_divisors(int x)
{vector<int> res;for (int i = 1; i <= x / i; i ++ )if (x % i == 0){res.push_back(i);if (i != x / i) res.push_back(x / i);}sort(res.begin(), res.end());return res;
}
4.试除法分解质因数
void divide(int x)
{for (int i = 2; i <= x / i; i ++ )if (x % i == 0){int s = 0;while (x % i == 0) x /= i, s ++ ;//合数一定会被分解成质数cout << i << ' ' << s << endl;}if (x > 1) cout << x << ' ' << 1 << endl;cout << endl;
}
2.数据结构
1.并查集
(1)朴素并查集:
struct DSU {std::vector<int> f, siz;DSU() {}DSU(int n) {init(n);}void init(int n) {f.resize(n);std::iota(f.begin(), f.end(), 0);siz.assign(n, 1);}int find(int x) {while (x != f[x]) {x = f[x] = f[f[x]];}return x;}bool same(int x, int y) {return find(x) == find(y);}bool merge(int x, int y) {x = find(x);y = find(y);if (x == y) {return false;}siz[x] += siz[y];f[y] = x;return true;}int size(int x) {return siz[find(x)];}
};
(2)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离// 返回x的祖宗节点,带权并查集
int find(int x)//更新到祖宗节点的距离
{if (p[x] != x){int t = p[x];p[x] = find(p[x]);d[x] += d[t];}return p[x];
}// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{p[i] = i;d[i] = 0;
}// 合并a和b所在的两个集合
pa = find(a), pb = find(b);//b合并到a
d[pb] = d[pa];
d[pa] += d[pb];
p[pb] = pa;// 根据具体问题,初始化find(a)的偏移量c
2.\(RMQ\)(ST表)--区间最值查询
区间查询用到st表,只能操作静态表,需要修改时,需要额外操作
首先定义\(dp[i,j]\)为以第i个数为起点,长度为2^j的一段区间中的最大值,显然状态转移为
$ dp[i,j] = max(dp[i,j-1],[i+2^(j-1),j-1]);$
查询时把区间分成前后都为长度为len=r-l+1的区间,/k为区间的最大倍增数/,左边是(l,2k),右边是(r-2k+1,r)
结果为 max(dp[l][k],dp[r-(1<<j)+1],[k])
换底公式:k=log2(n)=log(n)/log(2); math里的函数
初始化for(int j=0;j<M;j++)for(int i=1;i+(1<<j)-1<=n;i++)//中间是区间边界if(!j) dp[i][j] = w[i];else dp[i][j] = max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
查询
return max(dp[l][k],[r-(1<<k)+1][k]);
struct ST
{int n, q;vector<int> lg;vector<vector<int>> f;ST(int _n) : n(_n), lg(n + 3, 0), f(25, vector<int>(n + 1, 0)) {}void init(vector<int> &a){for (int i = 1; i <= n; i++)f[0][i] = a[i];for (int j = 1; j <= 20; j++)for (int i = 1; i + (1 << j) - 1 <= n; i++)f[j][i] = gcd(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);}int query(int l, int r){int len = __lg(r - l + 1);return gcd(f[len][l], f[len][r - (1 << len) + 1]);}
}
3. 树状数组板子
一般树状数组
struct BIT
{vector<int> tree;int N;BIT() {}BIT(int n){init(n);}void init(int n) {N = n, tree.resize(n);}void update(int x, int k){while (x < tree.size()) {tree[x] += k;x += x & -x;}}int query(int x){int res = 0;while (x > 0) {res += tree[x];x &= x - 1;}return res;}
};
4.封装线段树
1.结构体
const int mod = .....;
struct SegmentTree
{struct node{int l, r;//int val;int sum = 0;int mul = 1;int add = 0;}tr[N * 4];void push_up(int u){tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % mod;}void build(int u, int l, int r){if(l == r){tr[u] = {l, r, a[l] % mod};return ;}tr[u] = {l, r};int mid = l + r >> 1;build(u << 1, l, mid);build(u << 1 | 1, mid + 1, r);push_up(u);}void settag(int u, int mu, int ad)//lazy标记的修改{tr[u].mul = tr[u].mul * mu % mod ;tr[u].add = (tr[u].add * mu + ad) % mod;tr[u].sum = (tr[u].sum * mu + ((tr[u].r - tr[u].l + 1) * ad) % mod) % mod;}void push_down(int u){if(tr[u].mul != 1 || tr[u].add != 0){settag(u << 1, tr[u].mul, tr[u].add); settag(u << 1 | 1, tr[u].mul, tr[u].add);tr[u].mul = 1, tr[u].add = 0;}}void updata(int u, int x, int y, int mu, int ad)//把val改成val * mu + ad, 加法是1,k, 单点是0, k, 乘法是 k, 0{if(x <= tr[u].l && tr[u].r <= y){settag(u, mu, ad);return ;}push_down(u);int mid = tr[u].l + tr[u].r >> 1;if(x <= mid) updata(u << 1, x, y, mu, ad);if(y > mid) updata(u << 1 | 1, x, y, mu, ad);push_up(u);}int query(int u, int x, int y){if(x <= tr[u].l && tr[u].r <= y) return tr[u].sum;push_down(u);int mid = tr[u].l + tr[u].r >> 1;int res = 0;if(x <= mid) res += query(u << 1, x, y) % mod;if(y > mid) res += query(u << 1 | 1, x, y) % mod;return res % mod;}
}segt;
/*segt.build(1,1,n);
segt.update(1,l,r,mu,ad)//修改为 val* mu + ad;
segt.query(1,l,r);*/
2.数组
struct segtree
{#define ls (u << 1)#define rs (u << 1 | 1)vector<int> mx, lazy;vector<int> a;segtree(int n){n = n * 4;mx.resize(n);lazy.resize(n);a.resize(n);}void push_up(int u){mx[u] = max(mx[ls], mx[rs]);}void build(int u, int l, int r){if(l == r){mx[u] = a[l];return ;}int mid = l + r >> 1;build(u << 1, l, mid);build(u << 1 | 1, mid + 1, r);push_up(u);}void settag(int u, int k)//lazy标记的修改{mx[u] += k;lazy[u] += k;}void push_down(int u){if(lazy[u]){settag(ls, lazy[u]);settag(rs, lazy[u]);lazy[u] = 0;}}void update(int u, int l, int r, int x, int y, int k)//把val加k{if(x <= l && r <= y){settag(u, k);return ;}push_down(u);int mid = l + r >> 1;if(x <= mid) update(ls, l, mid, x, y, k);if(y > mid) update(rs, mid + 1, r, x, y, k);push_up(u);}int query(int u, int l, int r, int x, int y){if(x <= l && r <= y) return mx[u];push_down(u);int mid = l + r >> 1;int res = -1;if(x <= mid) res = max(res, query(ls, l, mid, x, y));if(y > mid) res = max(res, query(rs, mid + 1, r, x, y));return res;}
};
3.杂项
结构体储存数的信息,比起数组,在操作传参时避免传参时过多而出错
struct Segment_Tree
{int l,r;LL sum; //这里可以维护任何满足区间加法的信息,这里就用区间求和了//根据情况tag标记
}tr[4 * N]; //要开四倍空间函数1———build
void build (int u,int l,int r) //当前正在下标为u的点,这个点表示的区间是[l,r]
{ if (l == r) {tr[u] = {l,r,a[l]};/a[i]为各个点的信息return ;}tr[u] = {l,r}; //记得存储当前点表示的区间,否则你会调上一整天int mid = l + r >> 1;build (u << 1,l,mid),build (u << 1 | 1,mid + 1,r); //u << 1就是u * 2,u << 1 | 1就是u * 2 + 1push_up (u); //push_up函数的意思是把某一个节点的信息有他的子节点算出来
}
函数2.push_up
void push_up (int u) //这里只有区间和,区间和就是由一个点的左右子节点的和相加
{ tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;//向下递归更新
}
函数3.modify1.单点修改思路: 我们通过二分查找的形式找到要修改的点,然后把找的过程上的链都修改一下。时间复杂度 O(logn)void modify (int u,int x,int d) //当前这个点是下标为u的点,要把第x个数修改成d{ if (tr[u].l == tr[u].r) {tr[u].sum = d; //如果当前区间只有一个数,那么这个数一定是要修改的。return ;}int mid = tr[u].l + tr[u].r >> 1;if (x <= mid) modify (u << 1,x,d); //如果在左边就递归修改左半边区间else modify (u << 1 | 1,x,d); //如果在右边就递归修改右半边区间push_up (u) //记得更新信息}2.区间修改思路: lazy标记法用它统一记录区间的修改,而不是一个个修改区间内的值void push_down (int u) //下传标记函数{ auto &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1]; //加了引用符号只要修改这个变量就会修改他被赋值的变量if (root.sum_tag) //有懒标记才下传{ left.tag += root.tag,left.sum += (LL)(left.r - left.l + 1) * root.tag; //这里的子节点要加上懒标记,因为这里懒标记的意思是不包括当前节点right.tag += root.tag,right.sum += (LL)(right.r - right.l + 1) * root.tag; //同理root.tag = 0; //懒标记记得清零}}void modify (int u,int l,int r,int d) //当前遍历到的点下标是u,要将区间[l,r]增加d{ if (l <= tr[u].l && tr[u].r <= r) {tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;tr[u].sum_tag += d; //这里我才用了y总的懒标记的意思,一个点所有的子节点都加上d,而前一行时加上根节点的数,因为不包括根节点。return ;}push_down (u); //一定要分裂,只要记牢在递归左右区间之前,就要分裂int mid = tr[u].l + tr[u].r >> 1; //注意时tr[u].l和tr[u].rif (l <= mid) modify (u << 1,l,r,d); //左边有修改区间,就递归左半边if (r >= mid + 1) modify (u << 1 | 1,l,r,d); //右边有修改区间,就递归右半边push_up (u); //要记得要把这个点的信息更新一下
}
LL query (int u,int l,int r) {if (c[u].l >= l && c[u].r <= r) return c[u].v;int mid = c[u].l+c[u].r >> 1;LL ans = 0;if (l <= mid) ans = max (ans,query (u*2,l,r));if (r > mid) ans = max (ans,query (u*2+1,l,r));return ans;
}
4.重载线段树
struct Info {//一定要初始化ll sum;ll lazy = 0;int siz = 1;Info(int x) {sum = x;}Info() {sum = 0;}
};
Info merge(const Info &a, const Info &b) {Info c;return c;
}
struct segtree {#define ls (u << 1)#define rs (u << 1 | 1)int n;segtree(int n) {init(n);};vector<Info> info;vector<int> a;void init(int n) {this->n = n;info.resize(n << 2);a.resize(n << 1);}void push_up(int u) {info[u] = merge(info[ls], info[rs]);}void build(int u, int l, int r) {if (l == r) {info[u] = Info();//填值return ;}int mid = l + r >> 1;build(u << 1, l, mid);build(u << 1 | 1, mid + 1, r);push_up(u);}void settag(int u, int k) {//处理数据}void push_down(int u) {if (info[u].lazy) {settag(ls, info[u].lazy);settag(rs, info[u].lazy);info[u].lazy = 0;}}void update(int u, int l, int r, int pos, int k) {if (l == r) {info[u] = Info(k);return;}push_down(u);int mid = l + r >> 1;if (pos <= mid) update(ls, l, mid, pos, k);else update(rs, mid + 1, r, pos, k);push_up(u);}void update(int u, int l, int r, int x, int y, int k) {if (x <= l && r <= y) {settag(u, k);return;}push_down(u);int mid = l + r >> 1;if (x <= mid) update(ls, l, mid, x, y, k);if (mid < y) update(rs, mid + 1, r, x, y, k);push_up(u);}void update(int pos, int v) {update(1, 1, n, pos, v);}void update(int x, int y, int k) {update(1, 1, n, x, y, k);}Info query(int u, int l, int r, int x, int y) {if (x <= l && r <= y) return info[u];push_down(u);int mid = l + r >> 1;if (y <= mid) return query(ls, l, mid, x, y);else if (mid < x) return query(rs, mid + 1, r, x, y);else return merge(query(ls, l, mid, x, y), query(rs, mid + 1, r, x, y));}Info query(int l, int r) {return query(1, 1, n, l, r);}};
5.重链刨分
需要结合线段是来维护要求的数据
dfs1 是为预处理出每个节点的深度,重儿子,和子树大小
dfs2 是为了处理出每个重链的dfs序,l[u] 以为u根开始的dfs序, r[u]为重链尾部的dfs序( r[u] = l[u] + siz[u] - 1);
两个dfs函数的起点都是根节点
build时改为
tr[u] = {l, r, a[id[l]]}
struct heavyPathDecomposition
{int n, tot, rt;vector<int> g[N];int hs[N], siz[N], dep[N], fa[N];int top[N], l[N], r[N];SegmentTree segt;void addedge(int u, int v){g[u].push_back(v);g[v].push_back(u);}void dfs1(int u, int f){dep[u] = dep[f] + 1, siz[u] = 1, hs[u] = -1;fa[u] = f;for(auto v : g[u]){if(v == f) continue;dfs1(v, u);siz[u] += siz[v];if(hs[u] == -1 || siz[v] > siz[hs[u]]) hs[u] = v;}}void dfs2(int u, int topu){top[u] = topu;l[u] = ++ tot;id[tot] = u;if(hs[u] != -1) dfs2(hs[u], topu);for(auto v : g[u]){if(v == fa[u] || v == hs[u]) continue;dfs2(v, v);}r[u] = tot;}int qrang(int x, int y){int res = 0;while(top[x] != top[y]){if(dep[top[x]] < dep[top[y]]) swap(x, y);res = (res + segt.query(1, l[top[x]], l[x]) % mod) % mod;x = fa[top[x]];}if(dep[x] < dep[y]) swap(x, y);res = (res + segt.query(1, l[y], l[x]) % mod) % mod;return res;}int uprang(int x, int y, int k){while(top[x] != top[y]){if(dep[top[x]] < dep[top[y]]) swap(x, y);segt.updata(1, l[top[x]], l[x], 1, k);x = fa[top[x]];}if(dep[x] < dep[y]) swap(x, y);segt.updata(1, l[y], l[x], 1, k);}int qson(int x){return segt.query(1, l[x], r[x]) % mod;}void upson(int x, int k){segt.updata(1, l[x], r[x], 1, k);}
} hp;
/*hp.init(n);
hp.addedge(x, y);
hp.init2();
hp.uprang(x,y,k);
hp.qrang(x, y);
hp.qson(x, y)
hp.upson(int x, k)*/
6. 扫描线(二维数点问题)、
主要思想,从上到下的一个扫描线,形成的矩形为它的两个x边界和它的y和下个点的y,下面的边对应这块开始有贡献(即tag=1),上边对应这块贡献消失(tag=-1),存下每个点的信息,节点多一层,开八倍,其他二倍,和普通的线段树有一点区别
int n;
struct node
{int l, r;int cnt, len;
}tr[N << 3];
struct line
{int x1, x2, y;int tag;bool operator < (const line&_) const{return y < _.y;}
}L[N << 1];
int x[N << 1];
void build(int u, int l, int r)
{tr[u] = {l, r, 0, 0};if(l == r)return ;int mid = l + r >> 1;build(u << 1, l, mid);build(u << 1 | 1, mid + 1, r);
}
void push(int u)
{int l = tr[u].l, r = tr[u].r;if(tr[u].cnt) tr[u].len = x[r + 1] - x[l];else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
}
void updata(int u, int l, int r, int tag)
{//if(l > tr[u].r || r < tr[u].l) return ;//不可少if(l <= tr[u].l && tr[u].r <= r){tr[u].cnt += tag;push(u);return ;}//push_down(u);//每个标记独立,lazy标记不用下传if(l <= mid) updata(u << 1, l, r, tag);//和上面判断选一个if(r > mid) updata(u << 1 | 1, l, r, tag);push(u);
}
int sum;
void solve()
{cin >> n;for(int i = 1; i <= n; i ++){int x1, x2, y1, y2;cin >> x1 >> y1 >> x2 >> y2;//cout << x1 << ' ' << x2 << endl;L[i] = {x1, x2, y1, 1};L[i + n] = {x1, x2, y2, -1};x[i] = x1, x[i + n] = x2;}n *= 2;sort(L + 1, L + 1 + n);sort(x + 1, x + 1 + n);//for(int i = 1; i <= 2 * n; i ++) cout << x[i] << ' '; cout << endl;int s = unique(x + 1, x + 1 + n) - x - 1;build(1, 1, s - 1);//cout << n << ' ' << s << endl;for(int i = 1; i < n; i ++){auto [x1, x2, yy, tag] = L[i];//cout << x1 << ' ' << x2 << ' ' << yy << ' ' << tag << endl;int l = lower_bound(x + 1, x + 1 + s, x1) - x;int r = lower_bound(x + 1, x + 1 + s, x2) - x;//cout << x1 << ' ' << l << ' ' << x2 << ' ' << r << endl;updata(1, l, r - 1, tag);sum += tr[1].len * (L[i + 1].y - L[i].y);}cout << sum << endl;
}
7.分块
初始化
const int MAXN = 1000005, SQ = 1005;
int st[SQ], ed[SQ], size[SQ], bel[MAXN], sum[SQ];
int n, sq;
// vector<int> v[SQ], 有时可能遇到查找问题,可以维护整块排序后的数组,整块进行询问时直接二分查找。
void init() // 初始化
{sq = sqrt(n);for (int i = 1; i <= sq; ++i){st[i] = n / sq * (i - 1) + 1;ed[i] = n / sq * i;}ed[sq] = n;for (int i = 1; i <= sq; ++i)for (int j = st[i]; j <= ed[i]; ++j)bel[j] = i;for (int i = 1; i <= sq; ++i)size[i] = ed[i] - st[i] + 1;
}
区间修改
if (bel[x] == bel[y]) //当x与y在同一块内时,直接暴力修改原数组和sum数组: for (int i = x; i <= y; ++i){A[i] += k;sum[bel[i]] += k;}
for (int i = x; i <= ed[bel[x]]; ++i) //否则,先暴力修改左右两边的零散区间:
{A[i] += k;sum[bel[i]] += k;
}
for (int i = st[bel[y]]; i <= y; ++i)
{A[i] += k;sum[bel[i]] += k;
}
for (int i = bel[x] + 1; i < bel[y]; ++i) //然后对中间的整块打上标记:mark[i] += k;
区间查询
if (bel[x] == bel[y]) //同样地,如果左右两边在同一块,直接暴力计算区间和。for (int i = x; i <= y; ++i)s += A[i] + mark[bel[i]]; // 注意要加上标记
for (int i = x; i <= ed[bel[x]]; ++i) //否则,暴力计算零碎块:s += A[i] + mark[bel[i]];
for (int i = st[bel[y]]; i <= y; ++i)s += A[i] + mark[bel[i]];
for (int i = bel[x] + 1; i < bel[y]; ++i) //再处理整块:s += sum[i] + mark[i] * size[i]; // 注意标记要乘上块长
8.珂朵莉树(暴力骗分)
适用于随机生成的测试点, 适用于有推平操作, 维护区间信息, 区间第k大值, 区间和
struct node
{int l, r;multable ll v;node(ll l, ll r, ll v) : l(l), r(r), v(v) {};bool operator < (const node&_) const{return l < _.l;}
};
set<node> tr;//用set维护
auto split(ll pos)
{
auto split(ll pos)
{// 若不支持C++14,auto须改为set<node>::iteratorauto it = tree.lower_bound(node(pos, 0, 0)); // 寻找左端点大于等于pos的第一个节点if (it != tree.end() && it->l == pos) // 如果已经存在以pos为左端点的节点,直接返回return it;it--; // 否则往前数一个节点ll l = it->l, r = it->r, v = it->v;tree.erase(it); // 删除该节点tree.insert(node(l, pos - 1, v)); // 插入<l,pos-1,v>和<pos,r,v>return tree.insert(node(pos, r, v)).first; // 返回以pos开头的那个节点的迭代器
}
void add(int l, int r, ll v)// 区间操作
{auto end = split(r + 1), begin() = split(l);for(auto it = begin(); it != end; it ++){it->v += v;}tr.erase(begin(), end());tr.insert(node(l, r, v));
}
//区间第k小数
int kth(int l, int r, int k) {auto end = split(r + 1);vector<PII> rnk;for (auto it = split(l); it != end; ++it)rnk.emplace_back(it->v, it->r - it->l + 1);sort(rnk.begin(), rnk.end());int rnksz = rnk.size();for (int i = 0; i < rnksz; ++i) {k -= rnk[i].second;if (k <= 0) return rnk[i].first;}return -1;
}
9.莫队
一种离线的解决区间查询的算法,时间复杂度O(\(n\)\(\sqrt{n}\)),在O(1) 时间内从[\(l\), \(r\)] 转移到[\(l - 1\), \(r\)], [\(l + 1\), \(r\)], [\(l\), \(r - 1\)], [\(l\), \(r + 1\)];
int sq;
struct node
{int l, r, id;bool operator < (const node& _) const{if(l / sq != _.l / sq) return l < _.l;if(l / sq & 1) return r < _.r;//奇偶优化,方便转移return r > _.r; }
}que[N];
sq = sqrt(n);
sort(que, que + q);
auto add = [&](int p)//数的种类数量
{if(cnt[a[p]] == 0) tot ++;cnt[a[p]] ++:
};
auto del = [&](int p)
{cnt[a[p]] --;if(cnt[a[p]] == 0) tot --;
};
int l = 1, r = 0;
for(int i = 0; i < q; i ++)
{while(l > que[i].l) add(-- l);while(r < que[i].r) add(++ r);while(l < que[i].l) del(l ++);while(r > que[i].r) del(r --);res[que[i].id] = tot;
}
10. 主席树
1.(求区间不同数的数量)
右端点是第一次出现的点, 查询时查询r版本, 从l 开始查
#include<bits/stdc++.h>using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define endl "\n"
#define pb push_back
const int N=5e6+10;
struct node
{int l, r, sum;
}tr[N << 5];
int a[N], root[N];
unordered_map<int, int> lst;
int idx, n, m;
int build(int l, int r) {int rt = ++ idx;if(l == r) return rt;int mid = l + r >> 1;tr[rt].l = build(l, mid);tr[rt].r = build(mid + 1, r);return rt;
}
inline int insert(int pre, int l, int r, int x, int v){int rt = ++ idx;tr[rt] = tr[pre];if(l == r) {tr[rt].sum += v;return rt;}int mid = l + r >> 1;if(x <= mid) tr[rt].l = insert(tr[pre].l, l, mid, x, v);else tr[rt].r = insert(tr[rt].r, mid + 1, r, x, v);tr[rt].sum = tr[tr[rt].l].sum + tr[tr[rt].r].sum;return rt;
}
inline int query(int q, int l, int r, int pos) {if(l == r) return tr[q].sum;int mid = l + r >> 1;int res = 0;if(pos <= mid) res += query(tr[q].l, l, mid, pos) + tr[tr[q].r].sum;else res += query(tr[q].r, mid + 1, r, pos);return res;
}
int main()
{cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];//root[0] = build(1, n);for(int i = 1; i <= n; i ++) {if(!lst[a[i]]) {root[i] = insert(root[i - 1], 1, n, i, 1);} else {root[i] = insert(root[i - 1], 1, n, lst[a[i]], -1);root[i] = insert(root[i], 1, n, i, 1);}lst[a[i]] = i;}cin >> m;while(m --) {int x, y; cin >> x >> y;cout << query(root[y], 1, n, x) << endl;}
}
2(区间范围和)
区间历史值问题, 区间第k小, 区间小于某个数的和, 动态开点的权值线段树 ----- abc339G
#include<bits/stdc++.h>
//#define int long long
using namespace std;
using ull = unsigned long long;
using ll = long long;
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=998244353;
struct node {ll l, r;ll sum;
}tr[N << 5];
ll a[N], rt[N];
int n, q, idx;
ll res;
void push_up(int u) {//合并区间信息tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum;
}
int insert(int pre, int l, int r, int x){int u = ++ idx;tr[u] = tr[pre];if(l == r) {tr[u].sum += l;//权值线段树,一般加的是权值return u;}int mid = l + r >> 1;if(x <= mid) tr[u].l = insert(tr[pre].l, l, mid, x);else tr[u].r = insert(tr[pre].r, mid + 1, r, x);push_up(u);return u;
}
ll query(int u, int pre, int l, int r, int ql, int qr) {if(ql <= l && r <= qr) return tr[u].sum - tr[pre].sum;int mid = l + r >> 1;ll res = 0;if(ql <= mid) res += query(tr[u].l, tr[pre].l, l, mid, ql, qr);if(mid < qr) res += query(tr[u].r, tr[pre].r, mid + 1, r, ql, qr);return res;
}
signed main()
{IOS;cin >> n;for(int i = 1; i <= n; i ++) cin >> a[i];for(int i = 1; i <= n; i ++) {rt[i] = insert(rt[i - 1], 0, 1e9, a[i]);}cin >> q;while(q --) {ll l, r, k; cin >> l >> r >> k;l ^= res, r ^= res, k ^= res;res = query(rt[r], rt[l - 1], 0, 1e9, 0, k);cout << res << endl;}return 0;
}
3.区间第k小
#include<bits/stdc++.h>using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define pb push_back
const int N=1e5+10;
struct node {int l, r, sum;
}tr[N << 5];
int rt[N], a[N];
vector<int> lsh;
int n, idx, q;
int find(int x) {return lower_bound(lsh.begin(), lsh.end(), x) - lsh.begin();
}
void push_up(int u) {tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum;
}
int insert(int pre, int l, int r, int x){int u = ++ idx;tr[u] = tr[pre];if(l == r) {tr[u].sum ++;return u;}int mid = l + r >> 1;if(x <= mid) tr[u].l = insert(tr[pre].l, l, mid, x);else tr[u].r = insert(tr[pre].r, mid + 1, r, x);push_up(u);return u;
}
int query(int u, int pre, int l, int r, int k) {if(l == r) {return l;}int sum = tr[tr[u].l].sum - tr[tr[pre].l].sum;int mid = l + r >> 1;if(k <= sum) return query(tr[u].l, tr[pre].l, l, mid, k);else return query(tr[u].r, tr[pre].r, mid + 1, r, k - sum);
}
int main()
{IOS;cin >> n >> q;for(int i = 1; i <= n ;i ++) cin >> a[i], lsh.pb(a[i]);sort(lsh.begin(), lsh.end());lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());for(int i = 1; i <= n; i ++) {rt[i] = insert(rt[i - 1], 0, lsh.size() - 1, find(a[i]));}while(q --) {int x, y, k; cin >> x >> y >> k;int pos = query(rt[y], rt[x - 1], 0, lsh.size() - 1, k);cout << lsh[pos] << endl;}return 0;
}
4.区间前k小的和
注意到底部是
k * tr[l].val
, 离散或者不离散化都行E - Least Elements (atcoder.jp)
#include<bits/stdc++.h>
#define int long long
using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<ll,ll>;
using PIII = pair<ll, pair<ll,ll>>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define lowbit(x) (x) & (-x)
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
int root[N];
map<int, int> dy;
struct node {int l, r;int cnt, sum, val;
}tr[N << 5];
int idx;
int insert(int pre, int l, int r, int x, int v) {int p = ++ idx;tr[p] = tr[pre];if(l == r) {tr[p].sum += v;tr[p].cnt ++;tr[p].val = v;return p;}int mid = l + r >> 1;if(x <= mid) tr[p].l = insert(tr[pre].l, l, mid, x, v);else tr[p].r = insert(tr[pre].r, mid + 1, r, x, v);tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;tr[p].cnt = tr[tr[p].l].cnt + tr[tr[p].r].cnt;return p;
}
int query(int p, int pre, int l, int r, int k) {if(l == r) {return tr[p].val * k;}int cnt = tr[tr[p].l].cnt - tr[tr[pre].l].cnt;int res = 0, mid = l + r >> 1;if(k <= cnt) {res += query(tr[p].l, tr[pre].l, l, mid, k);} else {res += tr[tr[p].l].sum - tr[tr[pre].l].sum;res += query(tr[p].r, tr[pre].r, mid + 1, r, k - cnt);}return res;
}
signed main()
{int n, m, k; cin >> n >> m >> k;vector<ll> a(n + 1), lsh;//lsh.pb(-1e18);for(int i = 1; i <= n; i ++) {cin >> a[i];lsh.pb(a[i]);}sort(lsh.begin(), lsh.end());lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());auto get = [&](int x) {return lower_bound(lsh.begin(), lsh.end(), x) - lsh.begin();};for(int i = 1; i <= n; i ++) {dy[get(a[i])] = a[i];//cout << get(a[i]) << ' ' << a[i] << endl; }//for(int i = 0; i < lsh.size(); i ++) cout << i << ' ' << dy[i] << endl;for(int i = 1; i <= n; i ++) {root[i] = insert(root[i - 1], 0, lsh.size() - 1, get(a[i]), a[i]);//cout << get(a[i]) << endl;}for(int i = m; i <= n; i ++) {//cout << tr[root[i]].cnt << ' ' << tr[root[i - m]].cnt << endl;cout << query(root[i], root[i - m], 0, lsh.size() - 1, k) << ' ';}return 0;
}
11.平衡树
普通平衡树,基本功能是维护集合, 也能维护序列,就是BST
+Heaq
, 基本操作只有split
和 merge
注意split
有两种,按值分裂和按排名分裂
其他操作:比如插入,删除,求排名,求第k小,求第k小总和,区间反转,区间加减, 区间覆盖,区间max/min,有split
后一定有merge
,一般用插入建树
线段树也能维护序列, 也能维护每个数出现的次数(权值线段树)
1.维护集合(按值分裂)
P3369 【模板】普通平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<ll,ll>;
using PIII = pair<ll, pair<ll,ll>>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define lowbit(x) (x) & (-x)
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
mt19937 sj(111414);
struct {int ls, rs, val, key, siz;
}fhq[N];
int n, root, idx;
int xj(int v) {//新建节点fhq[++ idx] = {0, 0, v, (int)sj(), 1};return idx;
}
void push_up(int p) {//更新子树大小fhq[p].siz = fhq[fhq[p].ls].siz + fhq[fhq[p].rs].siz + 1;
}
void split(int p, int v, int &x, int &y){//分裂成以x, y编号为左右两颗子树,保证左子树的值都小于右子树if(!p) {x = y = 0;return ;}if(fhq[p].val <= v) {x = p;split(fhq[p].rs, v, fhq[p].rs, y);//因为x(左儿子)已经确定,所以去分裂右儿子,并传地址进去} else {y = p;split(fhq[p].ls, v, x, fhq[p].ls);//因为y(右儿子)已经确定,所以去分裂左儿子,并传地址进去}push_up(p);
}
int merge(int x, int y) {//递归合并分裂后的两颗子树if(!x || !y) return x + y;if(fhq[x].key < fhq[y].key) {//没有等于,保证全小于当前值的顺序fhq[x].rs = merge(fhq[x].rs, y);push_up(x);return x;} else {fhq[y].ls = merge(x, fhq[y].ls);push_up(y);return y;}
}
void insert(int v) {//插入后更新rootint x, y, z;split(root, v, x, z);y = xj(v);//y为单节点树root = merge(merge(x, y), z);//保证x, y, z的大小严格顺序
}
void erase(int v) {int x, y, z;split(root, v, x, z);split(x, v - 1, x, y);y = merge(fhq[y].ls, fhq[y].rs);//删根, 上面split保证一定存在root = merge(merge(x, y), z);//保证x, y, z的大小严格顺序
}
int find_rank(int v) {//查找v的排名,相同的第一个int x, y;split(root, v - 1, x, y);int res = fhq[x].siz + 1;root = merge(x, y);return res;
}
int kth(int k) {//第k小值int u = root;while(u) {int tmp = fhq[fhq[u].ls].siz + 1;if(tmp == k) break;else if(k < tmp)u = fhq[u].ls;else k -= tmp, u = fhq[u].rs;}return fhq[u].val;//返回信息可以修改
}
int find_pre(int u, int v) {if(u == 0) return -INF;if(fhq[u].val < v) {int res = find_pre(fhq[u].rs, v);return res == -INF ? fhq[u].val : res;} else {return find_pre(fhq[u].ls, v);}
}
int find_nxt(int u, int v) {if(u == 0) return INF;if(fhq[u].val > v) {int res = find_nxt(fhq[u].ls, v);return res == INF ? fhq[u].val : res;} else {return find_nxt(fhq[u].rs, v);}
}
int main()
{IOS;int n, m; cin >> n >> m;for(int i = 1; i <= n; i ++) {int x; cin >> x;insert(x);}int lst = 0, res = 0;while(m --) {int op, x; cin >> op >> x;//cout << op << ' ' << x << endl;x ^= lst;if(op == 1) {insert(x);} else if(op == 2) {erase(x);} else if(op == 3) {lst = find_rank(x);res ^= lst;//cout << lst << endl;} else if(op == 4) {lst = kth(x);res ^= lst;//cout << lst << endl;} else if(op == 5) {lst = find_pre(root, x);res ^= lst;//cout << lst << endl;} else {lst = find_nxt(root, x);res ^= lst;//cout << lst << endl;}}cout << res << endl;return 0;
}
2.维护序列(按排名分裂)
P3391 【模板】文艺平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<ll,ll>;
using PIII = pair<ll, pair<ll,ll>>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define lowbit(x) (x) & (-x)
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=1e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
struct FHQ {int ls, rs, val, key, siz;bool tag = 0;
}fhq[N];
mt19937 sj(111414);
int root, idx, n , m;
void push_up(int p) {fhq[p].siz = fhq[fhq[p].ls].siz + fhq[fhq[p].rs].siz + 1;
}
void push_down(int p) {if(fhq[p].tag) {swap(fhq[p].ls, fhq[p].rs);if(fhq[p].ls) fhq[fhq[p].ls].tag ^= 1;if(fhq[p].rs) fhq[fhq[p].rs].tag ^= 1;fhq[p].tag = 0;}
}
int xj(int v) {fhq[++ idx] = {0, 0, v, (int)sj(), 1, 0};return idx;
}
void split(int p, int k, int &x, int &y) {if(!p) {x = y = 0;return ;}push_down(p);int tmp = fhq[fhq[p].ls].siz + 1;if(k == tmp) {x = p;y = fhq[p].rs;fhq[p].rs = 0;} else if(k < tmp) {y = p;split(fhq[p].ls, k, x, fhq[p].ls);} else {x = p;split(fhq[p].rs, k - tmp, fhq[p].rs, y);}push_up(p);
}
int merge(int x, int y) {if(!x || !y) return x + y;if(fhq[x].key < fhq[y].key) {push_down(x);fhq[x].rs = merge(fhq[x].rs, y);push_up(x);return x;} else {push_down(y);fhq[y].ls = merge(x, fhq[y].ls);push_up(y);return y;}
}
void insert(int v) {int x, y, z; split(root, v, x, z);y = xj(v);root = merge(merge(x, y), z);}
void reverse(int l, int r) {int x, y, z;split(root, l - 1, x, y);split(y, r - l + 1, y, z);fhq[y].tag ^= 1;root = merge(merge(x, y), z);
}
void dfs(int p) {if(!p) return ;push_down(p);dfs(fhq[p].ls);cout << fhq[p].val << ' ';dfs(fhq[p].rs);
}
int main()
{IOS;cin >> n >> m;for(int i = 1; i <= n; i ++) {root = merge(root, xj(i));}while(m --) {int l, r; cin >> l >> r;reverse(l, r);}dfs(root);return 0;
}
3.图论
1.\(djikstra\)优化版
时间复杂度 O(mlogn), n 表示点数,m 表示边数
typedef pair<int, int> PII;int n; // 点的数量
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储所有点到1号点的距离
bool st[N]; // 存储每个点的最短距离是否已确定
vector<int> g[N];
int dij()
{memset(dis,0x3f,sizeof dis);dis[1]=0;priority_queue<PII,vector<PII>,greater<PII>>q;q.push({dis[1],1});while(q.size()){int u = q.top().second;q.pop();if(st[u]) continue;st[u] = true;for(auto [v, w] : g[u]){if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;q.push({dis[v],v});}}}return dis[n];
}
2..\(spfa\)
时间复杂度 平均情况下 O(m),最坏情况下 O(nm), n 表示点数,m 表示边数
int n; // 总点数
int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储每个点到1号点的最短距离
bool st[N]; // 存储每个点是否在队列中void add(int a,int b,int c)
{w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{memset(dis,0x3f,sizeof dis);dis[1]=0;queue<pair<int,int>>q;q.push({dis[1],1});st[1]=true;while(q.size()){auto t=q.front();q.pop();int tt=t.second;st[tt]=false;for(int i=h[tt];i!=-1;i=ne[i]){int j=e[i];if(dis[j]>dis[tt]+w[i]){dis[j]=dis[tt]+w[i];if(!st[j]){st[j]=true;q.push({dis[j],j});}}}}return dis[n];
}
3.\(kruskal\)
时间复杂度是 O(mlogm), n 表示点数,m 表示边数
点的成本可以看作是点到某个某个特别点的边权
int n, m; // n是点数,m是边数
int p[N]; // 并查集的父节点数组struct Edge // 存储边
{int a, b, w;bool operator< (const Edge &W)const{return w < W.w;}}edges[M];int find(int x) // 并查集核心操作
{if (p[x] != x) p[x] = find(p[x]);return p[x];
}int kruskal()
{sort(edges, edges + m);for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集int res = 0, cnt = 0;for (int i = 0; i < m; i ++ ){int a = edges[i].a, b = edges[i].b, w = edges[i].w;a = find(a), b = find(b);if (a != b) // 如果两个连通块不连通,则将这两个连通块合并{p[a] = b;res += w;cnt ++ ;}}if (cnt < n - 1) return INF;return res;}
4.有向图的拓扑序列
序列保存在orz中
void add(int a,int b)
{e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool top()
{for(int i=1;i<=n;i++){if(d[i]==0) q.push(i);}while(q.size()){int t=q.front();//cout<<t<<endl;orz[cnt++]=t;q.pop();for(int i=h[t];i!=-1;i=ne[i]){int j=e[i];d[j]--;if(d[j]==0) q.push(j);}}if(cnt<n) return 0;else return 1;}
5.\(floyd\)算法
时间复杂度是 O(n3), n 表示点数
初始化:for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )if (i == j) d[i][j] = 0;else d[i][j] = INF;// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{for (int k = 1; k <= n; k ++ )for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
6.LCA--最近公共祖先
(1).向上标记法( O(n) )
(2).倍增
fa[i,j]标识从i开始,向上跳2^j步所能走到的节点,0<=j<=logn
depth[i]表示深度
步骤:
[1].先让两个点跳到同一层
[2].让两个点同时往上跳,一直跳到它们公共祖先的下一层
预处理O(nlogn)
查询O(logn)
(3).tarjan( O(n + m) )
在深度优先遍历的同时, 将所有点分成三类
[1].已经遍历过的点, 且回溯过的点
[2].正在搜索的分支
[3].还未搜索到的点
倍增法
//倍增处理
void bfs()
{//memset(dis, 0x3f, sizeof dis);memset(depth, 0x3f, sizeof depth);queue<int> q;depth[0] = 0, depth[1]=1;//并非是1,是root//dis[1] = 0;q.push(1);while(q.size()){int u = q.front();q.pop();for(auto &[v, w] : g[u]){if(depth[v]>depth[u] + 1){depth[v] = depth[u] + 1;q.push(v);fa[v][0] = u;for(int k = 1; k <= 13; k ++)fa[v][k]=fa[fa[v][k-1]][k-1];dis[v] = dis[u] + w;//权值, 到公共祖先的距离}}}
}
void dfs(int root, int fno) {// 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。fa[root][0] = fno;dep[root] = dep[fa[root][0]] + 1;// 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第// 2^(i-1) 的祖先节点。for (int i = 1; i < 31; ++i) {fa[root][i] = fa[fa[root][i - 1]][i - 1];cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1];}// 遍历子节点来进行 dfs。for(auto v : g[root]){if(v == fno) continue;cost[v[root][i]][0] = w[root][i];dfs(v[root][i], root);}
//倍增查询
int lca(int a, int b)
{if(depth[a] < depth[b]) swap(a, b); //a在下面for(int k = 13; k >= 0; k --)if(depth[fa[a][k]] >= depth[b])a=fa[a][k];//跳到同一层if(a == b) return a; for(int k = 13; k >=0 ; k --)if(fa[a][k] != fa[b][k])a = fa[a][k], b = fa[b][k];//一起向上跳,跳到公共祖先的下一层return fa[a][0];
}
// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lca(int x, int y) {// 令 y 比 x 深。if (dep[x] > dep[y]) swap(x, y);// 令 y 和 x 在一个深度。int tmp = dep[y] - dep[x], ans = 0;for (int j = 0; tmp; ++j, tmp >>= 1)if (tmp & 1) ans += cost[y][j], y = fa[y][j];// 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。if (y == x) return ans;// 不然的话,找到第一个不是它们祖先的两个点。for (int j = 30; j >= 0 && y != x; --j) {if (fa[x][j] != fa[y][j]) {ans += cost[x][j] + cost[y][j];x = fa[x][j];y = fa[y][j];}}// 返回结果。ans += cost[x][0] + cost[y][0];return ans;
}
tarjan
int find(int x)//合并祖先节点
{if(x != p[x]) p[x] = find(p[x]);return p[x];
}
void tarjan(int u)//st[u]等于1,2可以直接合在一起,因为每次遍历时都会先对子节点进行标记
{//cout << u << endl;st[u] = 1;for(auto &[v, w] : g[u]){if(!st[v]){dis[v] = dis[u] + w;tarjan(v);p[v] = u;}}for(auto &[v, id] : query[u])//离线做法的体现if(st[v] == 2) res[id] = dis[u] + dis[v] - dis[find(v)] * 2;//cout << id << ' ' << dis[u] << ' ' << dis[v] << ' ' << res[id] << endl;st[u] = 2;
}
重链刨分(HLD)
struct HLD {int n;std::vector<int> siz, top, dep, parent, in, out, seq;std::vector<std::vector<int>> adj;int cur;HLD() {}HLD(int n) {init(n);}void init(int n) {this->n = n;siz.resize(n);top.resize(n);dep.resize(n);parent.resize(n);in.resize(n);out.resize(n);seq.resize(n);cur = 0;adj.assign(n, {});}void addEdge(int u, int v) {adj[u].push_back(v);adj[v].push_back(u);}void work(int root = 0) {top[root] = root;dep[root] = 0;parent[root] = -1;dfs1(root);dfs2(root);}void dfs1(int u) {if (parent[u] != -1) {adj[u].erase(std::find(adj[u].begin(), adj[u].end(), parent[u]));}siz[u] = 1;for (auto &v : adj[u]) {parent[v] = u;dep[v] = dep[u] + 1;dfs1(v);siz[u] += siz[v];if (siz[v] > siz[adj[u][0]]) {std::swap(v, adj[u][0]);}}}void dfs2(int u) {in[u] = cur++;seq[in[u]] = u;for (auto v : adj[u]) {top[v] = v == adj[u][0] ? top[u] : v;dfs2(v);}out[u] = cur;}int lca(int u, int v) {while (top[u] != top[v]) {if (dep[top[u]] > dep[top[v]]) {u = parent[top[u]];} else {v = parent[top[v]];}}return dep[u] < dep[v] ? u : v;}int dist(int u, int v) {return dep[u] + dep[v] - 2 * dep[lca(u, v)];}int jump(int u, int k) {//k级祖宗if (dep[u] < k) {return -1;}int d = dep[u] - k;while (dep[top[u]] > d) {u = parent[top[u]];}return seq[in[u] - dep[u] + d];}bool isAncester(int u, int v) {//u是不是v祖先return in[u] <= in[v] && in[v] < out[u];}int rootedParent(int u, int v) {std::swap(u, v);if (u == v) {return u;}if (!isAncester(u, v)) {return parent[u];}auto it = std::upper_bound(adj[u].begin(), adj[u].end(), v, [&](int x, int y) {return in[x] < in[y];}) - 1;return *it;}int rootedSize(int u, int v) {if (u == v) {return n;}if (!isAncester(v, u)) {return siz[v];}return n - siz[rootedParent(u, v)];}int rootedLca(int a, int b, int c) {return lca(a, b) ^ lca(b, c) ^ lca(c, a);}
};
7.匈牙利算法
时间复杂度O(nm),实际远小于
int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过bool find(int u)
{for (auto v : g[u]){if (!st[v]){st[v] = true;if (match[v] == 0 || find(match[v])){match[v] = u;return true;}}}return false;
}
// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ )
{memset(st, false, sizeof st);if (find(i)) res ++ ;
}
8.染色法判断二分图
int n; // n表示点数
int h[N], e[M], ne[M], idx; // 邻接表存储图
int color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{color[u] = c;for(auto v : g[u]){if (color[v] == -1){if (!dfs(v, !c)) return false;}else if (color[v] == c) return false;}return true;
}bool check()
{memset(color, -1, sizeof color);bool flag = true;for (int i = 1; i <= n; i ++ )if (color[i] == -1)if (!dfs(i, 0)){flag = false;break;}return flag;
}
9.有向图的强连通分量(\(scc\))
int dfn[N], low[N];
int stk[N], id[N], siz[N], instk[N];
int scc, top, y, tmtp;//dfs序
void targan(int u)
{dfn[u] = low[u] = ++ tmtp;instk[u] = stk[++ top] = u;for(auto v : g[u]){if(!dfn[v]){targan(v);low[u] = min(low[u], low[v]);}else if(instk[v]) low[u] = min(low[u], dfn[v]);}if(dfn[u] == low[u]){++ scc;do{y = stk[top --];instk[y] = false;id[y] = scc;siz[scc] ++;}while(y != u);}
}
10.重构树板子(最大化路径的最小值)
最大生成树中, 求x到y的最小路径, 最小瓶颈路
int n, m;
int p[N], w[N];
int fa[N][40], val[N][40];
int dep[N];
struct nd {int v, w;
};
vector<PII> g[N];
struct node {int u, v, w;bool operator < (const node &_) const{return w > _.w;}
}edges[N];
int find(int x) {if(x != p[x]) p[x] = find(p[x]);return p[x];
}
void kruskal() {for(int i = 1; i <= n; i ++) p[i] = i;sort(edges + 1, edges + 1 + m);for(int i = 1; i <= m; i ++) {auto [u, v, w] = edges[i];int fu = find(u), fv = find(v);if(fu == fv) continue;p[fu] = fv;g[u].pb({v, w}), g[v].pb({u, w});}
}
void dfs(int u, int lst) {dep[u] = dep[lst] + 1;fa[u][0] = lst;//cout << u << ' ' << lst << endl;for(int i = 1; i <= 30; i ++) {val[u][i] = min(val[u][i - 1], val[fa[u][i - 1]][i - 1]);fa[u][i] = fa[fa[u][i - 1]][i - 1];}for(auto [v, w] : g[u]) {if(v == lst) continue;val[v][0] = w;dfs(v, u);}
}
int lca(int a, int b) {int res = INF;if(dep[a] < dep[b]) swap(a, b);//cout << a << ' ' << b << endl;for(int k = 30; k >= 0; k --) {if(dep[fa[a][k]] >= dep[b]){res = min(res, val[a][k]);a = fa[a][k];}}if(a == b) return res;for(int k = 31; k >= 0; k --) {if(fa[a][k] != fa[b][k]) {res = min(res, val[a][k]);res = min(res, val[b][k]);a = fa[a][k], b = fa[b][k];}}return min({res, val[a][0], val[b][0]});
}
void solve()
{IOS;cin >> n >> m;for(int i = 1; i <= m; i ++) {int x, y, z; cin >> x >> y >> z;edges[i] = {x, y, z};}kruskal();for(int i = 1; i <= n ;i ++) {if(!dep[i]) {dfs(i, 0);}}int q; cin >> q;while(q -- ){int u, v; cin >> u >> v;if(find(u) != find(v)) cout << -1 << endl;else cout << lca(u, v) << endl;}
}
11.割边与割边缩点(EBCC)
std::set<std::pair<int, int>> E;struct EBCC {int n;std::vector<std::vector<int>> adj;std::vector<int> stk;std::vector<int> dfn, low, bel;int cur, cnt;EBCC() {}EBCC(int n) {init(n);}void init(int n) {this->n = n;adj.assign(n, {});dfn.assign(n, -1);low.resize(n);bel.assign(n, -1);stk.clear();cur = cnt = 0;}void addEdge(int u, int v) {adj[u].push_back(v);adj[v].push_back(u);}void dfs(int x, int p) {dfn[x] = low[x] = cur++;stk.push_back(x);for (auto y : adj[x]) {if (y == p) {continue;}if (dfn[y] == -1) {E.emplace(x, y);dfs(y, x);low[x] = std::min(low[x], low[y]);} else if (bel[y] == -1 && dfn[y] < dfn[x]) {E.emplace(x, y);low[x] = std::min(low[x], dfn[y]);}}if (dfn[x] == low[x]) {int y;do {y = stk.back();bel[y] = cnt;stk.pop_back();} while (y != x);cnt++;}}std::vector<int> work() {dfs(0, -1);return bel;}struct Graph {int n;std::vector<std::pair<int, int>> edges;std::vector<int> siz;std::vector<int> cnte;};Graph compress() {Graph g;g.n = cnt;g.siz.resize(cnt);g.cnte.resize(cnt);for (int i = 0; i < n; i++) {g.siz[bel[i]]++;for (auto j : adj[i]) {if (bel[i] < bel[j]) {g.edges.emplace_back(bel[i], bel[j]);//缩点后的新图的边连接的两个点//g.edges.emplace_back(i, j);//原图割边连接的两个点} else if (i < j) {g.cnte[bel[i]]++;}}}return g;}
};
12.斯坦纳树
-
求给定连通图 G 中的 n 个点与 k 个关键点,连接 k 个关键点,使得生成树的所有边的权值和最小, 近似算法,类似最小生成树, 时间复杂度\(n * 3^k + (n + m)logm\)
定义$ dp [s] [i] $ 为 以i为根,包含集合 s 中所有点的最小边权和,初始除了关键点全为 \(inf\) 考虑转移
对于度数 > 1的点, $ dp[s][i] = min(dp[s][i], dp[sub][i] + dp[s - sub][i]) \(, 这里的\)sub\(指的是\)s$的非空子集, 枚举非空子集的方法
for(int sub = s; sub; sub = (sub - 1) & s)
对于度数 = 1的点,可以用最短路进行松弛操作,类似三角不等式,
\(dp[s][i]\) 开始跑最短路,代表当前\(s\)集合中以\(i\)为根的集合能到达的最近点
涉及到状态压缩,建议从下标 0 开始,对于这k个点,初始状态可以定义为 \(dp[1 << i][key] = 0\) 代表第key个关键点在集合内的代价为0G - Last Major City
signed main()
{int n, m, k; cin >> n >> m >> k;vector<vector<PII>> g(n);for(int i = 1; i <= m; i ++) {int u, v, w; cin >> u >> v >> w;u --, v --;g[u].push_back({v, w}), g[v].push_back({u, w});}vector<vector<ll>> dp(1<< (k - 1), vector<ll>(n, 1e18));for(int i = 0; i < k - 1 ;i ++) {dp[1 << i][i] = 0;}for(int s = 0; s < (1 << (k - 1)); s ++) {priority_queue<PII, vector<PII>, greater<>> q;for(int i = 0; i < n; i ++) {for(int sub = s; sub; sub = (sub - 1) & s) {dp[s][i] = min(dp[s][i], dp[sub][i] + dp[s - sub][i]);}if(dp[s][i] != 1e18) {q.push({dp[s][i], i});}}vector<int> st(n, 0);while(q.size()) {auto [d, u] = q.top(); q.pop();if(st[u]) continue;st[u] = 1;for(auto [v, w] : g[u]) if (dp[s][v] > dp[s][u] + w) {dp[s][v] = dp[s][u] + w;q.push({dp[s][v], v});}}}for(int i = k - 1; i < n; i ++) cout << dp[(1 << (k - 1)) - 1][i] << endl;return 0;
}
13点的双连通分量(圆方树)
对于每一个点双连通分量,都建立一个方点,因为点双的性质,一个点双内的任意两点,都能通过点双内其他任意一点相互到达,对于割点向方点建边,方点向点双内其他所有点建边同时记录父亲,每次
tajan
后记得把最后一个点弹出
int fa[M], low[M], dfn[M];
int tot, tmsp, y;
vector<int> stk, g[M], h[M];
void tarjan(int u) {low[u] = dfn[u] = ++ tmsp;stk.push_back(u);for(auto v : g[u]) {if(!dfn[v]) {tarjan(v);low[u] = min(low[u], low[v]);if(low[v] == dfn[u]) {tot ++;do {y = stk.back();stk.pop_back();fa[y] = tot;h[tot].push_back(y);h[y].push_back(tot);}while(v != y);fa[tot] = u;h[tot].push_back(u);h[u].push_back(tot);}} else low[u] = min(low[u], dfn[v]);}
}
14.网络流
1.最大流
MaxFlow<ll> mf(n + 10);//初始化
mf.edges()//里面flow为存的残留(用过的边),cap为边容量,from, to存的为起点和终点
constexpr int inf = 1E9;
template<class T>
struct MaxFlow {struct _Edge {int to;T cap;_Edge(int to, T cap) : to(to), cap(cap) {}};int n;std::vector<_Edge> e;std::vector<std::vector<int>> g;std::vector<int> cur, h;MaxFlow() {}MaxFlow(int n) {init(n);}void init(int n) {this->n = n;e.clear();g.assign(n, {});cur.resize(n);h.resize(n);}bool bfs(int s, int t) {h.assign(n, -1);std::queue<int> que;h[s] = 0;que.push(s);while (!que.empty()) {const int u = que.front();que.pop();for (int i : g[u]) {auto [v, c] = e[i];if (c > 0 && h[v] == -1) {h[v] = h[u] + 1;if (v == t) {return true;}que.push(v);}}}return false;}T dfs(int u, int t, T f) {if (u == t) {return f;}auto r = f;for (int &i = cur[u]; i < int(g[u].size()); ++i) {const int j = g[u][i];auto [v, c] = e[j];if (c > 0 && h[v] == h[u] + 1) {auto a = dfs(v, t, std::min(r, c));e[j].cap -= a;e[j ^ 1].cap += a;r -= a;if (r == 0) {return f;}}}return f - r;}void addEdge(int u, int v, T c) {g[u].push_back(e.size());e.emplace_back(v, c);g[v].push_back(e.size());e.emplace_back(u, 0);}T flow(int s, int t) {T ans = 0;while (bfs(s, t)) {cur.assign(n, 0);ans += dfs(s, t, std::numeric_limits<T>::max());}return ans;}std::vector<bool> minCut() {std::vector<bool> c(n);for (int i = 0; i < n; i++) {c[i] = (h[i] != -1);}return c;}struct Edge {int from;int to;T cap;T flow;};std::vector<Edge> edges() {std::vector<Edge> a;for (int i = 0; i < e.size(); i += 2) {Edge x;x.from = e[i + 1].to;x.to = e[i].to;x.cap = e[i].cap + e[i + 1].cap;x.flow = e[i + 1].cap;a.push_back(x);}return a;}
};
2.费用流
边里面是(u, v, c, w)//起点,终点,容量,费用
template<class T>
struct MinCostFlow {struct _Edge {int to;T cap;T cost;_Edge(int to_, T cap_, T cost_) : to(to_), cap(cap_), cost(cost_) {}};int n;std::vector<_Edge> e;std::vector<std::vector<int>> g;std::vector<T> h, dis;std::vector<int> pre;bool dijkstra(int s, int t) {dis.assign(n, std::numeric_limits<T>::max());pre.assign(n, -1);std::priority_queue<std::pair<T, int>, std::vector<std::pair<T, int>>, std::greater<std::pair<T, int>>> que;dis[s] = 0;que.emplace(0, s);while (!que.empty()) {T d = que.top().first;int u = que.top().second;que.pop();if (dis[u] != d) {continue;}for (int i : g[u]) {int v = e[i].to;T cap = e[i].cap;T cost = e[i].cost;if (cap > 0 && dis[v] > d + h[u] - h[v] + cost) {dis[v] = d + h[u] - h[v] + cost;pre[v] = i;que.emplace(dis[v], v);}}}return dis[t] != std::numeric_limits<T>::max();}MinCostFlow() {}MinCostFlow(int n_) {init(n_);}void init(int n_) {n = n_;e.clear();g.assign(n, {});}void addEdge(int u, int v, T cap, T cost) {g[u].push_back(e.size());e.emplace_back(v, cap, cost);g[v].push_back(e.size());e.emplace_back(u, 0, -cost);}std::pair<T, T> flow(int s, int t) {T flow = 0;T cost = 0;h.assign(n, 0);while (dijkstra(s, t)) {for (int i = 0; i < n; ++i) {h[i] += dis[i];}//if(h[t] > 0) returen make_pair(flow, cost);//可行流T aug = std::numeric_limits<int>::max();for (int i = t; i != s; i = e[pre[i] ^ 1].to) {aug = std::min(aug, e[pre[i]].cap);}for (int i = t; i != s; i = e[pre[i] ^ 1].to) {e[pre[i]].cap -= aug;e[pre[i] ^ 1].cap += aug;}flow += aug;cost += aug * h[t];}return std::make_pair(flow, cost);}struct Edge {int from;int to;T cap;T cost;T flow;};std::vector<Edge> edges() {std::vector<Edge> a;for (int i = 0; i < e.size(); i += 2) {Edge x;x.from = e[i + 1].to;x.to = e[i].to;x.cap = e[i].cap + e[i + 1].cap;x.cost = e[i].cost;x.flow = e[i + 1].cap;a.push_back(x);}return a;}
};
4.字符串
1.字符串哈希
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{h[i] = h[i - 1] * P + str[i];p[i] = p[i - 1] * P;
}// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{return h[r] - h[l - 1] * p[r - l + 1];
}
struct Hash{#define maxn 200005ll hs1[maxn],hs2[maxn],pw1[maxn],pw2[maxn];inline void init(string s,const ll p1=998244353,const ll p2=19660813){hs1[0]=hs2[0]=0;pw1[0]=pw2[0]=1;for(ll i=1;i<s.size();++i){hs1[i]=(hs1[i-1]*131%p1+s[i]-'a'+1)%p1;hs2[i]=(hs2[i-1]*131%p2+s[i]-'a'+1)%p2;pw1[i]=pw1[i-1]*131%p1;pw2[i]=pw2[i-1]*131%p2;}}inline pair<ll,ll> qry(ll l,ll r,const ll p1=998244353,const ll p2=19660813){ll res1=(hs1[r]-hs1[l-1]*pw1[r-l+1]%p1+p1)%p1;ll res2=(hs2[r]-hs2[l-1]*pw2[r-l+1]%p2+p2)%p2;return make_pair(res1,res2);}
};
//hash h1, h2
//h1.init(s1), h2.init(s2) 下标从1开始
//h1.qry(l1, r2) == h2.qry(l2, r2)
2.KMP
vector<int> calcPi(string& p) {vector<int> pi(p.size());int match = 0;for (int i = 1; i < p.size(); i++) {char v = p[i];while (match > 0 && p[match] != v) {match = pi[match - 1];}if (p[match] == v) {match++;}pi[i] = match;}return pi;
}// 在文本串 s 中查找模式串 p,返回所有成功匹配的位置(p[0] 在 s 中的下标)
vector<int> kmp_search(string& s, string p) {if (p.empty()) {// s 的所有位置都能匹配空串,包括 s.size()vector<int> pos(s.size() + 1);iota(pos.begin(), pos.end(), 0);return pos;}vector<int> pi = calcPi(p);vector<int> pos;int match = 0;for (int i = 0; i < s.size(); i++) {char v = s[i];while (match > 0 && p[match] != v) {match = pi[match - 1];}if (p[match] == v) {match++;}if (match == p.size()) {pos.push_back(i - p.size() + 1);match = pi[match - 1];}}return pos;
}
3.字典树(\(trie\))
字典树用于解决字符串的前缀及后缀问题,以及数的异或查找(可撤销的(01 \(trie\) )
int son[N][26], cnt[N*26], idx;void insert(string s)
{int p = 0;for (int i = 0; i < s.size(); i ++ ){int u = s[i] - 'a';if (!son[p][u]) son[p][u] = ++ idx;p = son[p][u];cnt[p] ++ ;}}
int query(string s)
{int p = 0, res = 0;for (int i = 0; i < s.size(); i ++ ){int u = s[i] - 'a';if (!son[p][u]) return 0;p = son[p][u];res += cnt[p];}return res;
}
4.Manacher
std::vector<int> manacher(std::string s) {std::string t = "#";for (auto c : s) {t += c;t += '#';}int n = t.size();std::vector<int> r(n);for (int i = 0, j = 0; i < n; i++) {if (2 * j - i >= 0 && j + r[j] > i) {r[i] = std::min(r[2 * j - i], j + r[j] - i);}while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) {r[i] += 1;}if (i + r[i] > j + r[j]) {j = i;}}return r;
}
5.杂项
1.s中t字符串的数量
阿宁的毒瘤题
//dp[ s.size() ] [ t.size() ]
//dp[0] [j] = 0; //初始化
//dp[i] [0]= 1; (包含空串)
//dp[0] [0]= 1 (防止第一个条件覆盖
int tnum(string s, string t)
{dp[0][0] = 1;for(int i = 1; i <= s.size(); i ++){dp[i][0] = 1;for(int j = 1; j <= t.size(); j ++){dp[i][j] = dp[i - 1][j];if(s[i] == t[j]) dp[i][j] += dp[i - 1][j - 1];}}return dp[s.size()][t.size()];
}
//一维数组
int tnumn(string s, string t)
{for(int i = 0; i < s.size(); i ++){ for(int j = t.size() - 1; j >= 1; j --){if(s[i] == str[j]){ans[j] += ans[j - 1];}}if(s[i] == str[0]) {ans[0] += 1;}} return ans[2];
}
6.exKMP
z[i]代表s与s[i ~ n - 1]的lcp
求字符串的匹配数,找 p + "#" + t的z函数
考虑计算 s 的 Z 函数,则其整周期的长度为最小的 n 的因数 i,满足 i+z[i]=n
本质不同字串
vector<int> zfunction(string s) {int n = (int)s.size();vector<int> z(n);z[0] = n;for (int i = 1, l = 0, r = 0; i < n; ++i) {if (i <= r && z[i - l] < r - i + 1) {z[i] = z[i - l];//盒内包含前缀,对应点的z不超过盒外,可以直接转移} else {z[i] = max(0, r - i + 1);//对应点的z超过盒外,先取靠右while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];//后暴力更新}if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;//更新盒子边界}return z;
}
5. 数学
1. 快速幂
ll ksm(ll a,ll b)
{ll res = 1;while(b){if(b & 1) res = (ll)res*a % mod;a = (ll)a * a % mod;b >>= 1;}return res;
}
ll ksms(ll x, string p) {//字符串快速幂LL res = 1;x %= mod;for (int i = p.size() - 1; i >= 0; --i) {int digit = p[i] - '0';ll temp = 1;for (int j = 0; j < digit; ++j) {temp = (temp * x) % mod;}res = (res * temp) % mod;x = (x * x) % mod;}return res;
}
2.拓展欧几里得(\(exgcd\))
原理 : $gcd(a, b) = gcd(b, a $ % $ b)$ , x, y是 \(ax + by = c\) 的一个特解, 这里一定要 $c $ %$ gcd(a, b) == 0$, 否则无解
而我们可以用扩展欧几里得求得特解,剩下得就是求齐次解了
齐次方程为 \(ax + by = 0\);
求得的解为 \(x = k * (b / gcd(a , b)); y = - k * (a / gcd(a , b))\);
所以 \(ax + by = c\) 的通解为
\(x = x0(特解) + k *(b / gcd(a,b)); y = y0(特解) - k *(a / gcd(a,b))\);
int exgcd(int a, int b, int &x, int &y)
{if (!b){x = 1; y = 0;return a;}int d = exgcd(b, a % b, y, x);y -= (a/b) * x;return d;
}
3.求组合数的方法
struct Comb {const int N;vector<ll> fac, invfac;Comb(int n) : N(n), fac(n + 2), invfac(n + 2) { init(); };ll qpow(ll x, ll p) {ll res = 1 % mod; x %= mod;for (; p; p >>= 1, x = x * x % mod)if (p & 1) res = res * x % mod;return res;}ll inv(ll x) { return qpow(x, mod - 2); };void init() {fac[0] = 1;for (int i = 1; i <= N; ++i) fac[i] = fac[i - 1] * i % mod;invfac[N] = inv(fac[N]);for (int i = N - 1; i >= 0; --i) invfac[i] = (invfac[i + 1] * (i + 1)) % mod;}ll C(int n, int m) {if (n < m || m < 0) return 0;return fac[n] * invfac[m] % mod * invfac[n - m] % mod;}ll A(int n, int m) {if (n < m || m < 0) return 0;return fac[n] * invfac[n - m] % mod;}
};
4. 矩阵快速幂求斐波那契数列
n 很大时,要用矩阵快速幂求解,时间复杂度是 \(log N\)
#include <iostream>
#include <cstring>#define Max_rank 3
#define mod 1000000007
struct Matrix {long long a[Max_rank][Max_rank];Matrix() {memset(a, 0, sizeof(a));}void init(){a[1][1] = a[1][2] = a[2][1] = 1;a[2][2] = 0;}Matrix operator*(const Matrix b) {Matrix res;for (int i = 1; i <= 2; i++)for (int j = 1; j <= 2; j++)for (int u = 1; u <= 2; u++)res.a[i][j] = (res.a[i][j] + a[i][u]*b.a[u][j])%mod;return res;}
};long long q_pow(long long n){Matrix ans,base;ans.init();base.init();//这里要填矩阵值while(n > 0){if(n&1) ans =ans *base;base = base *base;n >>= 1;}return ans.a[1][1];
}
int main() {long long n;while(std::cin >> n){std::cout << q_pow(n-2) << std::endl;//迭代次数自定义,一般为n - 1次}return 0;
}
5.欧拉函数
//1 - n 中与n互质的个数
int phi(int x)
{int res = x;for (int i = 2; i <= x / i; i ++ )if (x % i == 0){res = res / i * (i - 1);while (x % i == 0) x /= i;}if (x > 1) res = res / x * (x - 1);return res;
}
6. 素数测试与因式分解(Miller-Rabin & Pollard-Rho)
i64 mul(i64 a, i64 b, i64 m) {return static_cast<__int128>(a) * b % m;
}
i64 power(i64 a, i64 b, i64 m) {i64 res = 1 % m; for (; b; b >>= 1, a = mul(a, a, m))if (b & 1)res = mul(res, a, m);return res;
}
bool isprime(i64 n) {if (n < 2)return false;static constexpr int A[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};int s = __builtin_ctzll(n - 1);i64 d = (n - 1) >> s;for (auto a : A) {if (a == n)return true;i64 x = power(a, d, n);if (x == 1 || x == n - 1)continue;bool ok = false;for (int i = 0; i < s - 1; ++i) {x = mul(x, x, n);if (x == n - 1) {ok = true;break;}}if (!ok)return false;}return true;
}
std::vector<i64> factorize(i64 n) {std::vector<i64> p;std::function<void(i64)> f = [&](i64 n) {if (n <= 10000) {for (int i = 2; i * i <= n; ++i)for (; n % i == 0; n /= i)p.push_back(i);if (n > 1)p.push_back(n);return;}if (isprime(n)) {p.push_back(n);return;}auto g = [&](i64 x) {return (mul(x, x, n) + 1) % n;};i64 x0 = 2;while (true) {i64 x = x0;i64 y = x0;i64 d = 1;i64 power = 1, lam = 0;i64 v = 1;while (d == 1) {y = g(y);++lam;v = mul(v, std::abs(x - y), n);if (lam % 127 == 0) {d = std::gcd(v, n);v = 1;}if (power == lam) {x = y;power *= 2;lam = 0;d = std::gcd(v, n);v = 1;}}if (d != n) {f(d);f(n / d);return;}++x0;}};f(n);std::sort(p.begin(), p.end());return p;
}
int idx;//质因数组合
int p[100010], cnt[100010];//存质因子
vector<int> getDivisor()
{vector<int> divisor(100010, 0);divisor[0] = 1;divisor[1] = 1;for (int i = 1; i <= p[0]; ++i){int nowNum = divisor[0];int base = 1;for (int j = 1; j <= cnt[i]; ++j){base *= p[i];for (int k = 1; k <= divisor[0]; ++k)divisor[++nowNum] = divisor[k] * base;}divisor[0] = nowNum;}return divisor;
}
7.高斯消元
1.异或方程组(abc366_g)
#include<bits/stdc++.h>using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define IOS ios::sync_with_stdio(false),cin.tie(0)
#define lowbit(x) (x) & (-x)
#define endl "\n"
#define pb push_back
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=1e2+10;
const int INF=0x3f3f3f3f;
const int mod=998244353;bitset<N> g[N];
ll res[N];
void solve()
{int n, m; cin >> n >> m;for(int i = 1; i <= m; i ++) {int u, v; cin >> u >> v;g[u][v] = g[v][u] = 1;}ll w = 1;for(int i = 1; i <= n; i ++) {int pos = i;while(pos <= n && g[pos][i] == 0) pos ++;if(pos > n) {//多解自由元res[i] = w;w <<= 1;continue;} if(pos != i) swap(g[i], g[pos]); for(int j = 1; j <= n; j ++) {if(g[j][i] && i != j) {g[j] ^= g[i]; }}}for(int i = 1; i <= n; i ++) {if(res[i]) continue;for(int j = 1; j <= n; j ++) {if(g[i][j] && i != j) {//b列值为0这样操作res[i] ^= res[j];}}if(res[i] == 0) {cout << "No" << endl;return;}}cout << "Yes" << endl;for(int i = 1; i <= n; i ++) {cout << res[i] << ' ';}
}
2.普通方程组
const double EPS = 1E-9;
int n;
vector<vector<double> > a(n, vector<double>(n));double det = 1;
for (int i = 0; i < n; ++i) {int k = i;for (int j = i + 1; j < n; ++j)if (abs(a[j][i]) > abs(a[k][i])) k = j;if (abs(a[k][i]) < EPS) {det = 0;break;}swap(a[i], a[k]);if (i != k) det = -det;det *= a[i][i];for (int j = i + 1; j < n; ++j) a[i][j] /= a[i][i];for (int j = 0; j < n; ++j)if (j != i && abs(a[j][i]) > EPS)for (int k = i + 1; k < n; ++k) a[j][k] -= a[i][k] * a[j][i];
}cout << det;
8.欧拉降幂
$ a^{b} \bmod c=a^{b \bmod \varphi(c)+\varphi(c)} \bmod c $
$ \varphi(c) $ 为欧拉函数
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int MAX=1000100;
ll fastPow(ll a,ll b,ll mod)
{ll ans=1;a %= mod;while(b){if(b&1){ans = (ans*a)%mod;}b >>= 1;a = (a*a)%mod;}return ans;
}
ll eulerFunction(ll x)
{ll eulerNumbers = x;for(ll i = 2; i*i <= x; i++){if(x % i == 0){eulerNumbers = eulerNumbers / i * (i-1);while(x % i == 0){x /= i;}}}if(x > 1){eulerNumbers = eulerNumbers / x * (x-1);}return eulerNumbers;
}
ll eulerDropPow(ll a,char b[],ll c)
{ll eulerNumbers = eulerFunction(c);ll descendingPower=0;for(ll i=0,len = strlen(b); i<len; ++i){descendingPower=(descendingPower*10+b[i]-'0') % eulerNumbers;}descendingPower += eulerNumbers;return fastPow(a,descendingPower,c);
}
int main()
{ll a,c;char b[MAX];while(~scanf("%lld%s%lld",&a,b,&c)) {printf("%lld\n",eulerDropPow(a,b,c));}return 0;
}
9.线性基
集合的最大异或和(所有的p异或), 最小异或和(最小的p), 第k大(小)异或和(p有t个元素,第k大就是2^t - k的二进制对应的行)
查询某个数是否能被异或出来,类似于插入,如果最后插入的数 p 被异或成了 0,则能被异或出来。
ll p[70];
bool zero;
void insert(ll x) {for(int i = 63; i >= 0; i --) {if(x >> i & 1) {if(p[i] == 0) {p[i] = x;return ;} else {x ^= p[i];}}}zero = true;
}
ll qmax(int k) {//第一大int cnt = 0;for(int i = 63; i >= 0; i --) cnt += (p[i] > 0);ll num = pow(2, cnt);k = num - k;ll res = 0;for(int i = 63; i >= 0; i --) {if(k >> 1 & 1)res = max(res, res ^ p[i]);}return res;
}
6.计算几何
1.求凸包
using i64 = double;
struct Point {i64 x;i64 y;Point(i64 x = 0, i64 y = 0) : x(x), y(y) {}
};bool operator==(const Point &a, const Point &b) {return a.x == b.x && a.y == b.y;
}Point operator+(const Point &a, const Point &b) {return Point(a.x + b.x, a.y + b.y);
}Point operator-(const Point &a, const Point &b) {return Point(a.x - b.x, a.y - b.y);
}i64 dot(const Point &a, const Point &b) {//点积return a.x * b.x + a.y * b.y;
}i64 cross(const Point &a, const Point &b) {//叉积return a.x * b.y - a.y * b.x;
}
i64 dis(const Point &a, const Point &b) {//return sqrt((a.x -b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); return sqrt((a - b).x * (a - b).x + (a - b).y * (a - b).y);
}
void norm(std::vector<Point> &h) {int i = 0;for (int j = 0; j < int(h.size()); j++) {if (h[j].y < h[i].y || (h[j].y == h[i].y && h[j].x < h[i].x)) {i = j;}}std::rotate(h.begin(), h.begin() + i, h.end());
}int sgn(const Point &a) {return a.y > 0 || (a.y == 0 && a.x > 0) ? 0 : 1;
}std::vector<Point> getHull(std::vector<Point> p) {std::vector<Point> h, l;std::sort(p.begin(), p.end(), [&](auto a, auto b) {if (a.x != b.x) {return a.x < b.x;} else {return a.y < b.y;}});p.erase(std::unique(p.begin(), p.end()), p.end());if (p.size() <= 1) {return p;}for (auto a : p) {while (h.size() > 1 && cross(a - h.back(), a - h[h.size() - 2]) <= 0) {h.pop_back();}while (l.size() > 1 && cross(a - l.back(), a - l[l.size() - 2]) >= 0) {l.pop_back();}l.push_back(a);h.push_back(a);}l.pop_back();std::reverse(h.begin(), h.end());h.pop_back();l.insert(l.end(), h.begin(), h.end());return l;
}
2.旋转卡壳
用来求凸包最大距离点对(多校),最短距离点对
int n;const double eps=1e-9;
int dcmp(double x){return (fabs(x)<=eps)?0:(x<0?-1:1);
}
struct Point{double x,y;Point(double X=0,double Y=0){x=X,y=Y;}
};
struct Vector{double x,y;Vector(double X=0,double Y=0){x=X,y=Y;}
};
inline Vector operator-(Point x,Point y){// 点-点=向量return Vector(x.x-y.x,x.y-y.y);
}
inline double cross(Vector x,Vector y){ // 向量叉积return x.x*y.y-x.y*y.x;
}
inline double operator*(Vector x,Vector y){ // 向量叉积return cross(x,y);
}
inline double len(Vector x){ // 向量模长return sqrt(x.x*x.x+x.y*x.y);
}int stk[50005];
bool used[50005];
vector<Point> ConvexHull(Point* poly, int n){ // Andrew算法求凸包int top=0;sort(poly+1,poly+n+1,[&](Point x,Point y){return (x.x==y.x)?(x.y<y.y):(x.x<y.x);});stk[++top]=1;for(int i=2;i<=n;i++){while(top>1&&dcmp((poly[stk[top]]-poly[stk[top-1]])*(poly[i]-poly[stk[top]]))<=0){used[stk[top--]]=0;}used[i]=1;stk[++top]=i;}int tmp=top;for(int i=n-1;i;i--){if(used[i]) continue;while(top>tmp&&dcmp((poly[stk[top]]-poly[stk[top-1]])*(poly[i]-poly[stk[top]]))<=0){used[stk[top--]]=0;}used[i]=1;stk[++top]=i;}vector<Point> a;for(int i=1;i<=top;i++){a.push_back(poly[stk[i]]);}return a;
}struct Line{Point x;Vector y;Line(Point X,Vector Y){x=X,y=Y;}Line(Point X,Point Y){x=X,y=Y-X;}
};inline double DistanceToLine(Point P,Line x){// 点到直线的距离Vector v1=x.y, v2=P-x.x;return fabs(cross(v1,v2))/len(v1);
}double RoatingCalipers(vector<Point> poly){// 旋转卡壳if(poly.size()==3) return len(poly[1]-poly[0]);int cur=0;double ans=0;for(int i=0;i<poly.size()-1;i++){Line line(poly[i],poly[i+1]);while(DistanceToLine(poly[cur], line) <= DistanceToLine(poly[(cur+1)%poly.size()], line)){cur=(cur+1)%poly.size();}ans=max(ans, max(len(poly[i]-poly[cur]), len(poly[i+1]-poly[cur])));}return ans;
}Point poly[50005];signed main(){//used,stk清空,1下标开始的数组cin>>n;for(int i=1;i<=n;i++) cin>>poly[i].x>>poly[i].y;double v=RoatingCalipers(ConvexHull(poly, n));cout<<(int)(v*v);return 0;
}
7.动态规划
1.数位dp
适用于大范围统计满足某个约束的数量,memo记忆化维度取决于除了后面的约束外的个数,例如数位之和可以加个\(sum\)。
将 n 转换成字符串 s,定义 \(f(i,lim,num)\) 表示构造从左往右第 \(i\) 位及其之后数位的合法方案数,其中:
\(lim\) 表示当前是否受到了 \(n\) 的约束。若为真,则第 \(i\) 位填入的数字至多为 \(s[i]\),否则至多为 \(9\)。例如 \(n=234\),如果前面填了 \(23\),那么最后一位至多填 \(4\);如果前面填的不是 \(23\),那么最后一位至多填 \(9\)。如果在受到约束的情况下填了 \(s[i]\),那么后续填入的数字仍会受到 \(n\) 的约束
\(num\) 表示 \(i\) 前面的数位是否填了数字。若为假,则当前位可以跳过(不填数字),或者要填入的数字至少为 \(1\);若为真,则必须填数字,且要填入的数字从 \(0\) 开始。这样我们可以控制构造出的是一位数/两位数/三位数等等。
对于一个固定的 \(i\),它受到 \(lim\) 或 \(num\) 的约束在整个递归过程中至多会出现一次,没必要记忆化。比如 \(n=234\),当 \(i=2\) 的时候,前面可以填 \(11,12,13,⋯,23\),如果受到 \(lim\) 的约束,就说明前面填的是 \(23\)。「当 \(i=2\) 的时候,前面填的是 23」这件事情,在整个递归过程中至多会出现一次。
另外,如果只记忆化 \(i,dp\) 数组的含义就变成在不受到 \(n\) 的约束时的合法方案数,所以要在!lim && num
成立时才去记忆化。接着上面的例子,在前面填 \(23\) 的时候,下一位填的数字不能超过 \(4\),因此算出来的结果是不能套用到前面填的是 \(11,12,13,⋯\) 这些数字上面的。多维也一样
void solve {int m = to_string(n).size();int memo[m];memset(m, -1, sizeof memo);auto dfs = [&](auto &&dfs, int i, bool lim, bool num) -> int {if(i == m) return num;if(!lim && num && memo[i] != -1) return memo[i];int res = 0;if(!num) res += dfs(dfs, i + 1, false, false);char up = lim ? s[i] : '9';for(int d = 1 - num; d <= up; d ++) {res += dfs(dfs, i + 1, lim && d == up, true);}if(!lim && num) memo[i] = res;return res;};dfs(dfs, 0, true, false);
}
8.杂项
1. __int128的输入输出
__int128 read(){__int128 x=0;bool f=0;char c=getchar();while (c<'0'||c>'9'){if (c=='-')f=1;c=getchar();}while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}return f?-x:x;
}
inline void write(__int128 x)
{if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10+'0');
}
2.如何找到p/q的循环节?
考虑1/q
1.如果q中仅含有2或5,那么该小数是有限循环小数
2.如果不含2或5,10与q互素,根据欧拉定理,10^Φ(q)≡1(%q),其中Φ(q)便是循环节,但不一定是最小的,因为最小的循环节重复k遍仍是循环节,所以通过遍历Φ(q)的因子找到最小的循环节即可。
3.如果出了2和5还有其他的,先找出2和5的个数分别为a与b,循环节前面一段的长度就是max(a,b),剩下的10与q除掉2a,5b互素,找到最小循环节即可。
#include<bits/stdc++.h>
//大整数取模要用__int128
using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
const int N=1e5+10;
const int INF=0x3f3f3f3f;
__int128 gcd(__int128 a, __int128 b) { return b ? gcd(b, a % b) : a;}
__int128 read(){__int128 x=0;bool f=0;char c=getchar();while (c<'0'||c>'9'){if (c=='-')f=1;c=getchar();}while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}return f?-x:x;
}
inline void write(__int128 x)
{if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10+'0');
}
__int128 phi(__int128 x)
{__int128 res = x;for(__int128 i = 2; i <= x / i; i ++)if(x % i == 0){res = res / i * (i - 1);while(x % i == 0) x /= i;}if(x > 1) res = res / x * (x - 1);return res;
}
__int128 ksm(__int128 a,__int128 b,__int128 mod)
{__int128 res = 1;while(b){if(b & 1) res = (__int128)res*a % mod;a = (__int128)a * a % mod;b >>= 1;}return res;
}
int main()
{__int128 p, q;p = read(), q = read();__int128 d = gcd(p, q);q /= d;__int128 cn2 = 0, cn5 = 0;while(q % 2 == 0) q /= 2, cn2 ++;while(q % 5 == 0) q /= 5, cn5 ++;//write(q);//cout << endl;if(q == 1){cout << "-1" << endl;return 0;}__int128 t = phi(q);__int128 res = 1e18;for(__int128 i = 1; i <= t / i; i ++){if(t % i == 0){__int128 x1 = i;__int128 x2 = t / i;if(ksm(10, x1, q) == 1) res = min(res, x1);if(ksm(10, x2, q) == 1) res = min(res, x2);}}write(max(cn2, cn5));cout << ' ';write(res);return 0;
}
3.pbds库
平衡树操作
insert(x):向树中插入一个元素 x,返回 std::pair<point_iterator, bool>。
erase(x):从树中删除一个元素/迭代器 x,返回一个 bool 表明是否删除成功。
order_of_key(x):返回 x 以 Cmp_Fn 比较的排名。
find_by_order(x):返回 Cmp_Fn 比较的排名所对应元素的迭代器。
lower_bound(x):以 Cmp_Fn 比较做 lower_bound,返回迭代器。
upper_bound(x):以 Cmp_Fn 比较做 upper_bound,返回迭代器。
join(x):将 x 树并入当前树,前提是两棵树的类型一样,x 树被删除。
split(x,b):以 Cmp_Fn 比较,小于等于 x 的属于当前树,其余的属于 b 树。
empty():返回是否为空。
size():返回大小。
文件定义
#include<bits/extc++.h>
using namespace __gnu_pbds;#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp> // 用treeusing namespace std;
using namespace __gnu_pbds;using TR = tree<ll, null_type, less<ll>, rb_tree_tag, tree_order_statistics_node_update>;
4. 对拍
//需要的文件
data.in
solve.out
std.out
diff.log
duipai.cpp
solve.cpp//现在代码
std.cpp//暴力正确代码
data.cpp//造数据
#include<bits/stdc++.h>using namespace std;int main() {int t = 0;while(1) {cout << "test : " << t ++ << endl;system("data.exe > data.in");system("std.exe < data.in > std.out");system("solve.exe < data.in > solve.out");if(system("fc std.out solve.out > diff.log")) {cout << "WA\n";break; }cout << "AC\n";}
}
//#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define random(a,b) ((a)+sj()%((b)-(a)+1))
typedef long long ll;
typedef unsigned long long ull;
int dsu[1000005];//1e6
int mapp[10000005];
int a[1000010];
mt19937 sj(time(0));
void create(){//随机树构造int n=random(1,10);for(int i=n;i>=2;--i){dsu[i]=random(1,i-1);}for(int i=1;i<=n;++i) mapp[i]=i;random_shuffle(mapp+1,mapp+n+1);printf("%d\n",n);for(int i=2;i<=n;++i){printf("%d %d\n",mapp[i],mapp[ dsu[i] ]);}return;
}
#include<bits/stdc++.h>//随机数using namespace std;
//#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define random(a,b) ((a)+sj()%((b)-(a)+1))
typedef long long ll;
typedef unsigned long long ull;
int dsu[1000005];//1e6
int mapp[10000005];
int a[1000010];
mt19937 sj(time(0));int main(){return 0;
}