算法竞赛模板
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. (ST表)--区间最值查询
区间查询用到st表,只能操作静态表,需要修改时,需要额外操作
首先定义为以第i个数为起点,长度为2^j的一段区间中的最大值,显然状态转移为
查询时把区间分成前后都为长度为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 + 1
push_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.modify
1.单点修改
思路: 我们通过二分查找的形式找到要修改的点,然后把找的过程上的链都修改一下。时间复杂度 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].r
if (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>::iterator
auto 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(
),在O(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
, 离散或者不离散化都行
#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) {//插入后更新root
int 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. 优化版
时间复杂度 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..
时间复杂度 平均情况下 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.
时间复杂度是 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. 算法
时间复杂度是 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.有向图的强连通分量( )
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 个关键点,使得生成树的所有边的权值和最小, 近似算法,类似最小生成树, 时间复杂度
定义
为 以i为根,包含集合 s 中所有点的最小边权和,初始除了关键点全为 考虑转移对于度数 > 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的点,可以用最短路进行松弛操作,类似三角不等式,
开始跑最短路,代表当前 集合中以 为根的集合能到达的最近点
涉及到状态压缩,建议从下标 0 开始,对于这k个点,初始状态可以定义为 代表第key个关键点在集合内的代价为0
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 200005
ll 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.字典树( )
字典树用于解决字符串的前缀及后缀问题,以及数的异或查找(可撤销的(01
)
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.拓展欧几里得( )
原理 :
% , x, y是 的一个特解, 这里一定要 % , 否则无解
而我们可以用扩展欧几里得求得特解,剩下得就是求齐次解了
齐次方程为;
求得的解为;
所以的通解为
;
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 很大时,要用矩阵快速幂求解,时间复杂度是
#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.欧拉降幂
为欧拉函数
#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记忆化维度取决于除了后面的约束外的个数,例如数位之和可以加个
。
将 n 转换成字符串 s,定义
表示构造从左往右第 位及其之后数位的合法方案数,其中: 表示当前是否受到了 的约束。若为真,则第 位填入的数字至多为 ,否则至多为 。例如 ,如果前面填了 ,那么最后一位至多填 ;如果前面填的不是 ,那么最后一位至多填 。如果在受到约束的情况下填了 ,那么后续填入的数字仍会受到 的约束
表示 前面的数位是否填了数字。若为假,则当前位可以跳过(不填数字),或者要填入的数字至少为 ;若为真,则必须填数字,且要填入的数字从 开始。这样我们可以控制构造出的是一位数/两位数/三位数等等。
对于一个固定的,它受到 或 的约束在整个递归过程中至多会出现一次,没必要记忆化。比如 ,当 的时候,前面可以填 ,如果受到 的约束,就说明前面填的是 。「当 的时候,前面填的是 23」这件事情,在整个递归过程中至多会出现一次。
另外,如果只记忆化数组的含义就变成在不受到 的约束时的合法方案数,所以要在 !lim && num
成立时才去记忆化。接着上面的例子,在前面填的时候,下一位填的数字不能超过 ,因此算出来的结果是不能套用到前面填的是 这些数字上面的。多维也一样
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> // 用tree
using 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;
}
本文作者:zouyua
本文链接:https://www.cnblogs.com/ZouYua/p/18742391
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步