退役了退役了,把自己的板子放一下,需要的自取
矩阵算法
扫描线线段树
单调栈
扫描线
int a[N][M], n, m;
int l[N][N], r[N][N], h[N][N];//向左,右,上最远
int read() {
char c = getchar();
while (c != '1' && c != '0')c = getchar();
return c == '1'; //返回1代表障碍
}
//切记mr=m+1!!!!
//遇到是障碍 ml=0,mr=m+1!!!!
int main() {
int ans = 0;
scc(n, m);
fir(i, 1, m) r[0][i] = m + 1;
fir(i, 1, n) {
int ml = 0, mr = m + 1;
fir(j, 1, m) {
a[i][j] = read();
if (a[i][j]) ml = j, l[i][j] = 0, h[i][j] = 0;
else l[i][j] = max(l[i - 1][j], ml), h[i][j] = h[i - 1][j] + 1;
}
fri(j, m, 1)
if (a[i][j]) mr = j, r[i][j] = m + 1;
else r[i][j] = min(r[i - 1][j], mr),
ans = max(ans, h[i][j] * (r[i][j] - l[i][j] - 1);
}
printf("%d", ans);
return 0;
}
障碍法
int n, m, s, ans = 0;
PII a[N];
//顺序不能改
inline bool work(int &up, int &down, int i, int j, int v)
{
if (v * (down - up) <= ans) return 1;
umax(ans, (down - up) * (abs(a[j].x - a[i].x)));
if (a[j].y<up || a[j].y>down)return 0;
//尽管不在上下界内,也要先算在返回,防止这个点不卡上下边界,但是最以后一个点而没算
if (a[j].y == a[i].y)return 1;
if (a[j].y < a[i].y)up = a[j].y;
else down = a[j].y;
return 0;
}
int main() {
sccc(n, m, s); //n*m网格, s个障碍, 复杂度只和s有关(O(s^2))
fir(i, 1, s) scc(a[i].fi, a[i].se);
a[++s] = { 0, 0 }; a[++s] = { n, 0 };
a[++s] = { 0, m }; a[++s] = { n, m };
sort(a + 1, a + 1 + s);
fir(i, 1, s) {
RE int up = 0, down = m, v = n - a[i].fi;
fir(j, i + 1, s) if (work(up, down, i, j, v)) break;
up = 0, down = m, v = a[i].fi;
fri(j, i - 1, 1) if (work(up, down, i, j, v)) break;
}
sort(a + 1, a + 1 + s, [&](PII x, PII y) {
return x.se != y.se ? x.se < y.se : x.fi < y.fi;
});
fir(i, 1, s - 1) umax(ans, m * (a[i + 1].se - a[i].se));
printf("%d", ans);
}
数据结构
并查集
带权
int n, f[N], dep[N], sz[N];
int find(int x) {
if (x == f[x]) return x;
if (f[x] == f[f[x]]) return f[x];
int fa = find(f[x]); dep[x] += dep[f[x]] - 1;
return f[x] = fa;
}
void unit(int x, int y) {
x = find(x), y = find(y);
if (x != y) dep[y] = sz[x] + 1, sz[f[y] = x] += sz[y];
}
分块
解决出现次数
#pragma GCC optimize(2)
const int N = 1e5 + 5, M = pow(N, 1.0 / 3) + 5;
int n, m, k, cnt[M][N], val[M][M], t, len, a[N], id[N];
vector<int> c;
void init() {
for (int i = 1; i <= n; ++i) cin >> a[i], c.push_back(a[i]);
sort(all(c)); c.erase(unique(all(c)), c.end());
t = pow(n, 1.0 / 3); len = (n - 1) / t + 1; t = (n - 1) / len + 1;
rep(i, 1, n) a[i]=lower_bound(all(c),a[i])-c.begin(), id[i]=(i-1)/len+1;
for (int i = t, res = 0; i; --i, res = 0) {
for (int j = min(n, i * len); j; --j) {
++cnt[i][a[j]];
if ((cnt[i][a[j]] & 1) && cnt[i][a[j]] - 1) --res;
else if (cnt[i][a[j]] - 1) ++res;
if (j % len == 1) val[id[j]][i] = res;
}
}
}
int work(int l, int L, int R, int r) { int ans = val[id[L]][id[R]];
for (int i = l; i < L; ++i) {
++cnt[id[L] - 1][a[i]];
if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]] & 1) --ans;
else if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]]) ++ans;
}
for (int i = r; i > R; --i) {
--cnt[id[R]][a[i]];
if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]] & 1) --ans;
else if (cnt[id[R]][a[i]] - cnt[id[L] - 1][a[i]]) ++ans;
}
for (int i = l; i < L; ++i) --cnt[id[L] - 1][a[i]];
for (int i = r; i > R; --i) ++cnt[id[R]][a[i]];
return ans;
}
int main() {
IOS; cin >> n >> k >> m; init();
for (int i = 0, ls = 0; i < m; ++i) {
int l, r; cin >> l >> r; l = (l + ls) % n + 1, r = (r + ls) % n + 1;
if (l > r) swap(l, r);
cout << (ls = work((id[l] - 1) * len + 1, l, r, min(id[r] * len, n))) << '\n';
}
return 0;
}
树状数组
区改单查
int a[N], c[N];
void add(int x, int k) { for (; x <= n; x += -x & x) c[x] += k; }
void add(int l, int r, int k) { add(l, k); add(r + 1, -k); }
int ask(int x) { int ans = 0; for (; x; x -= x & x) ans += c[x]; return ans; } //+a[x];
区查区改
int c[2][N], a[N], sum[N];
void add(int x,ll k) {for(int i=x;i<=n;i+=-i&i) c[0][i] += k, c[1][i] += x * k; }
void add(int l, int r, ll k) { add(l, k); add(r + 1, -k); }
ll ask(int x) {
ll p = 0, q = 0, f = x + 1;
for (; x; x -= -x & x) p += c[0][x], q += c[1][x];
return p * f - q;
}
ll ask(int l, int r) { return ask(r) - ask(l - 1); }//+sum[r] - sum[l - 1]
二维区改单查
ll c[N][M], a[N][M];
void add(int x, int y, ll k){
for (int t = y; x <= n; x += -x & x, y = t)
for (; y <= m; y += -y & y) c[x][y] += k;
}
void add(int x1, int y1, int x2, int y2, ll k) { //左上角, 右下角
add(x1, y1, k); add(x2 + 1, y2 + 1, k);
add(x1, y2 + 1, -k); add(x2 + 1, y1, -k);
}
ll ask(int x, int y) {
ll ans = 0;
for (int t = y; x <= n; x -= -x & x, y = t)
for (; y <= m; y -= -y & y) ans += c[x][y];
return ans;
}
二维区查区改
ll c[4][N][M], a[N][M], sum[N][M];
void add(int x, int y, ll k) {
for (int i = x; i <= n; i += -i & i)
for (int j = y; j <= m; j += -j & j) {
c[0][i][j] += k;
c[1][i][j] += k * x;
c[2][i][j] += k * y;
c[3][i][j] += k * x * y;
}
}
void add(int x1, int y1, int x2, int y2, ll k) {
add(x1, y1, k); add(x2 + 1, y2 + 1, k);
add(x1, y2 + 1, -k); add(x2 + 1, y1, -k);
}
ll ask(int x, int y) {
ll p = 0, q = 0, r = 0, t = 0;
for (int i = x; i; i -= -i & i)
for (int j = y; j; j -= -j & j)
p += c[0][i][j], q += c[1][i][j], r += c[2][i][j], t += c[3][i][j];
return p * (x + 1) * (y + 1) - q * (y + 1) - r * (x + 1) + t;
}
ll ask(int x1, int y1, int x2, int y2) {
return ask(x2, y2) - ask(x1 - 1, y2) - ask(x2, y1 - 1) + ask(x1 - 1, y1 - 1);
}
线段树
基本(区间和)
struct BitTree {
struct node { int l, r, val, tag, len; } tr[N << 2];
void push_up(int rt) { tr[rt].val = tr[rt<<1].val + tr[rt<<1|1].val; }
void push_down(int rt) {
tr[rt << 1].tag += tr[rt].tag, tr[rt << 1 | 1].tag += tr[rt].tag;
tr[rt << 1].val += tr[rt << 1].len * tr[rt].tag;
tr[rt << 1 | 1].val += tr[rt << 1 | 1].len * tr[rt].tag;
tr[rt].tag = 0;
}
void build(int rt, int l, int r, int* a) {
tr[rt] = {l, r, 0, 0, r - l + 1};
if (l == r) { tr[rt].val = a[l]; return; }
int mid = l + r >> 1;
build(rt << 1, l, mid, a); build(rt << 1 | 1, mid + 1, r, a);
push_up(rt);
}
void change(int rt, int l, int r, int k) {
if (r < l) return;
if (tr[rt].l >= l && tr[rt].r <= r) {
tr[rt].val += tr[rt].len * k;
tr[rt].tag += k; return;
} push_down(rt);
int mid = tr[rt].l + tr[rt].r >> 1;
if (l <= mid) change(rt << 1, l, r, k);
if (r > mid) change(rt << 1 | 1, l, r, k);
push_up(rt);
}
ll ask(int rt, int l, int r) {
if (r < l) return 0;
if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt].val;
push_down(rt);
int mid = tr[rt].l + tr[rt].r >> 1;
ll ans = l <= mid ? ask(rt << 1, l, r) : 0;
if (r > mid) ans += ask(rt << 1 | 1, l, r);
push_up(rt); return ans;
}
} bit;
扫描线面积(计算覆盖k次的面积)
struct BIT { //总的覆盖k次以上的长度为tr[1].val[k - 1]
static const int N = 2e3 + 5;
struct Node {
int l, r, len, val[3], cnt;//val[i]代表扫描i-1次的线段长度,cnt为覆盖次数
int& operator [](int k) { return val[k]; }
} tr[N << 2];
void push_up(int rt) {
rep (i, 0, 2)//rep(i, 0, k - 1)
if (tr[rt].cnt > i) tr[rt][i] = tr[rt].len;
else if (tr[rt].l + 1 == tr[rt].r) tr[rt][i] = 0;
else tr[rt][i] = tr[rt<<1][i - tr[rt].cnt] + tr[rt<<1|1][i - tr[rt].cnt];
}
void build(int rt, int l, int r, VI& a) {//a为离散化的x vector
tr[rt].l = l, tr[rt].r = r, tr[rt].len = a[r] - a[l];
tr[rt][0] = tr[rt][1] = tr[rt][2] = tr[rt].cnt = 0;
if (l + 1 == r) return;
int mid = l + r >> 1;
build(rt << 1, l, mid, a); build(rt << 1 | 1, mid, r, a);
}
void change(int rt, int l, int r, int k) {
if (tr[rt].l >= l && tr[rt].r <= r) { tr[rt].cnt += k; push_up(rt); return; }
int mid = tr[rt].l + tr[rt].r >> 1;
if (mid > l) change(rt << 1, l, r, k);
if (mid < r) change(rt << 1 | 1, l, r, k);
push_up(rt);
}
int ask(int rt, int l, int r, int k) {//覆盖k次以上的长度和
if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt][k];
int mid = tr[rt].l + tr[rt].r >> 1;
if (r <= mid) return ask(rt << 1, l, r, k);
if (l >= mid) return ask(rt << 1 | 1, l, r, k);
return ask(rt << 1, l, r, k) + ask(tr << 1 | 1, l, r, k);
}
} bit;
扫描线周长
struct BIT {
static const int N = 1e4 + 5;
struct node { int l, r, len, cnt, tag, val[2]; } tr[N << 2];
void push(int rt) {
if (tr[rt].tag) tr[rt].val[0] = tr[rt].val[1] = tr[rt].len, tr[rt].cnt = 1;
else if (tr[rt].l + 1 == tr[rt].r)
tr[rt].val[0] = tr[rt].val[1] = 0, tr[rt].cnt = 0;
else {
tr[rt].cnt = tr[rt << 1].cnt + tr[rt << 1 | 1].cnt -
(tr[rt << 1].val[1] && tr[rt << 1 | 1].val[0] ? 1 : 0);
tr[rt].val[0] = tr[rt << 1].val[0] +
(tr[rt << 1].val[0] ^ tr[rt << 1].len ? 0 : tr[rt << 1 | 1].val[0]);
tr[rt].val[1] = tr[rt << 1 | 1].val[1] +
(tr[rt << 1 | 1].val[1] ^ tr[rt << 1 | 1].len ? 0 : tr[rt << 1].val[1]);
}
}
void build(int rt, int l, int r, vector<int>& a) {
tr[rt].l = l, tr[rt].r = r, tr[rt].len = a[r] - a[l];
if (l + 1 == r) return;
int mid = l + r >> 1; build(rt << 1, l, mid, a); build(rt << 1 | 1, mid, r, a);
}
void change(int rt, int l, int r, int k) {
if (tr[rt].l >= l && tr[rt].r <= r) { tr[rt].tag += k; push(rt); return; }
int mid = tr[rt].l + tr[rt].r >> 1;
if (l < mid) change(rt << 1, l, r, k);
if (r > mid) change(rt << 1 | 1, l, r, k);
push(rt);
}
} bit;
struct line { int y, a, b, k; } a[10005];
int n, x[10000], y[10000];
long long work(int x[], int y[]) {
vector<int> c; c.push_back(-2e9);
for (int i = 0; i < n; ++i) {
a[i << 1] = { y[i << 1], x[i << 1], x[i << 1 | 1], 1 };
a[i << 1 | 1] = { y[i << 1 | 1], x[i << 1], x[i << 1 | 1], -1 };
c.push_back(x[i << 1]); c.push_back(x[i << 1 | 1]);
}
sort(c.begin(), c.end()); c.erase(unique(c.begin(), c.end()), c.end());
sort(a, a + n * 2, [](line& a, line& b){return a.y^b.y?a.y<b.y:a.k>b.k;});
long long ans = 0; bit.build(1, 1, c.size() - 1, c);
bit.change(1, lower_bound(c.begin(), c.end(), a[0].a) - c.begin(),
lower_bound(c.begin(), c.end(), a[0].b) - c.begin(), a[0].k);
for (int i = 1; i < n << 1; ++i) {
ans += (long long)bit.tr[1].cnt * (a[i].y - a[i - 1].y) << 1;
bit.change(1, lower_bound(c.begin(), c.end(), a[i].a) - c.begin(),
lower_bound(c.begin(), c.end(), a[i].b) - c.begin(), a[i].k);
}
return ans;
}
int main() {
cin >> n;
for (int i = 0; i < n; ++i) cin>>x[i<<1]>>y[i<<1]>>x[i<<1|1]>>y[i<<1|1];
cout << work(x, y) + work(y, x);
return 0;
}
主席树
可持续线段树
struct CHT {
static const int N = 1e5 + 5;
struct Node { int l, r; ll val, tag; } tr[N * 25];
int rt[N], tot; // 不可push_up/down
void build(int& rt, int l, int r, ll* a) {
tr[rt = ++tot] = { 0, 0, 0, 0 };
if (l == r) { tr[rt].val = a[l]; return; }
int mid = l + r >> 1;
build(tr[rt].l, l, mid, a); build(tr[rt].r, mid + 1, r, a);
tr[rt].val = tr[tr[rt].l].val + tr[tr[rt].r].val
}
void change(int& x, int y, int pl, int pr, int l, int r, ll k) {
tr[x = ++tot] = tr[y];
tr[x].val += (min(r, pr) - max(pl, l) + 1) * k;
if (l <= pl && pr <= r) { tr[x].tag += k; return; }
int mid = pl + pr >> 1;
if (l <= mid) change(tr[x].l, tr[y].l, pl, mid, l, r, k);
if (r > mid) change(tr[x].r, tr[y].r, mid + 1, pr, l, r, k);
}
ll ask(int rt, int pl, int pr, int l, int r) {
if (l <= pl && pr <= r) return tr[rt].val;
ll ans = (min(pr, r) - max(pl, l) + 1) * tr[rt].tag;
int mid = pl + pr >> 1;
if (mid >= l) ans += ask(tr[rt].l, pl, mid, l, r);
if (mid < r) ans += ask(tr[rt].r, mid + 1, pr, l, r);
return ans;
}
} T;
静态
struct CHT {
struct node { int val, lson, rson; } tr[20 * N]; //log2(sz)*n,sz值域
int root[N], tot;
/*void push_up(int rt) { tr[rt].val = tr[tr[rt].l].val + tr[tr[rt].r].val; }
//这样build tr 要 (log2(n) + 4) * n
void build(int &rt, int l, int r, int countofa[]) {
rt = ++tot; tr[rt].l = l, tr[rt].r = r;
if (l == r) { tr[rt].val = countofa[l]; return; }
int mid = l + r >> 1;
build(tr[rt].lson, l, mid, countofa);
build(tr[rt].rson, mid + 1, r, countofa);
push_up(rt);
} */
void update(int &x, int y, int l, int r, int k, int cnt) {
tr[x = ++tot] = tr[y]; tr[x].val += cnt;
if (l == r) return;
int mid = l + r >> 1;
if (k <= mid) update(tr[x].lson, tr[y].lson, l, mid, k, cnt);
else update(tr[x].rson, tr[y].rson, mid + 1, r, k, cnt);
}
int ask(int x, int y, int l, int r, int k) {
if (l == r) return l;
int mid = l + r >> 1, val = tr[tr[x].lson].val - tr[tr[y].lson].val;
if (val >= k) return ask(tr[x].lson, tr[y].lson, l, mid, k);
else return ask(tr[x].rson, tr[y].rson, mid + 1, r, k - val);
}
};
动态
const int N = 1e5 + 5;
struct CHT {
struct node { int val, lson, rson; } tr[N * 300]; //log2(sz)*log2(n)*n
int root[N], tot, lsc, rsc, sz, n; // n为数组大小,sc为值域
int ls[20], rs[20]; //log(n)
void build(int n, int* a, int sz) {
this->sz = sz; this->n = n;
for (int i = 1; i <= n; ++i) change(i, a[i], 1);
}
void update(int &rt, int l, int r, int k, int cnt) {
if (!rt) rt = ++tot;
tr[rt].val += cnt;
if (l == r) return;
int mid = l + r >> 1;
if (k <= mid) update(tr[rt].lson, l, mid, k, cnt);
else update(tr[rt].rson, mid + 1, r, k, cnt);
}
void change(int x, int k, int cnt) { //n为最大区间长度
for (int i = x; i <= n; i += i & -i) update(root[i], 1, sz, k, cnt);
}
int ask(int l, int r, int k) {
if (l == r) return l;
int sum = 0;
for (int i = 1; i <= lsc; ++i) sum -= tr[tr[ls[i]].lson].val;
for (int i = 1; i <= rsc; ++i) sum += tr[tr[rs[i]].lson].val;
int mid = l + r >> 1;
if (sum >= k) {
for (int i = 1; i <= lsc; ++i) ls[i] = tr[ls[i]].lson;
for (int i = 1; i <= rsc; ++i) rs[i] = tr[rs[i]].lson;
return ask(l, mid, k);
} else {
for (int i = 1; i <= lsc; ++i) ls[i] = tr[ls[i]].rson;
for (int i = 1; i <= rsc; ++i) rs[i] = tr[rs[i]].rson;
return ask(mid + 1, r, k - sum);
}
}
int preask(int l, int r, int k) {
lsc = rsc = 0;
for (int i = l; i; i -= -i & i) ls[++lsc] = root[i];
for (int i = r; i; i -= -i & i) rs[++rsc] = root[i];
return ask(1, sz, k);
}
} bit;
cout << c[bit.preask(q[i].l - 1, q[i].r, q[i].k)] << '\n';
bit.change(q[i].l, a[q[i].l], -1);
a[q[i].l] = lower_bound(all(c), q[i].k) - c.begin();
bit.change(q[i].l, a[q[i].l], 1);
树上动态第k大,通过dfs序维护
struct CHT {
static const int M = 6e7;
struct node {
int val, lson, rson; /*int l, r; 值域*/
node (int Val = 0, int Ls = 0, int Rs = 0)
: val(Val), lson(Ls), rson(Rs){}
} tr[N* 520]; //log2(sz)*log2(sz)*n
int root[N], tot, qsc, qtc, quc, qwc, sz, n; // n为数组大小,sc为值域
int qs[15], qt[15], qu[15], qw[15]; //log(n)
void build(int n, int sz, int *a, PII *dfn) {
this->sz = sz; this->n = n;
for (int i = 1; i <= n; ++i)
change(dfn[i].fi, a[i], 1), change(dfn[i].se + 1, a[i], -1);
}
void update(int &rt, int l, int r, int k, int cnt) {
if (!rt) rt = ++tot;
tr[rt].val += cnt;
if (l == r) return;
int mid = l + r >> 1;
if (k <= mid) update(tr[rt].lson, l, mid, k, cnt);
else update(tr[rt].rson, mid + 1, r, k, cnt);
}
void change(int x, int k, int cnt) { //n为最大区间长度
for (int i = x; i <= n; i += i & -i) update(root[i], 0, sz, k, cnt);
}
int ask(int l, int r, int k) {
if (l == r) return l;
int sum = 0;
for (int i = 1; i <= qsc; ++i) sum += tr[tr[qs[i]].lson].val;
for (int i = 1; i <= qtc; ++i) sum += tr[tr[qt[i]].lson].val;
for (int i = 1; i <= quc; ++i) sum -= tr[tr[qu[i]].lson].val;
for (int i = 1; i <= qwc; ++i) sum -= tr[tr[qw[i]].lson].val;
int mid = l + r >> 1;
if (sum >= k) {
for (int i = 1; i <= qsc; ++i) qs[i] = tr[qs[i]].lson;
for (int i = 1; i <= qtc; ++i) qt[i] = tr[qt[i]].lson;
for (int i = 1; i <= quc; ++i) qu[i] = tr[qu[i]].lson;
for (int i = 1; i <= qwc; ++i) qw[i] = tr[qw[i]].lson;
return ask(l, mid, k);
} else {
for (int i = 1; i <= qsc; ++i) qs[i] = tr[qs[i]].rson;
for (int i = 1; i <= qtc; ++i) qt[i] = tr[qt[i]].rson;
for (int i = 1; i <= quc; ++i) qu[i] = tr[qu[i]].rson;
for (int i = 1; i <= qwc; ++i) qw[i] = tr[qw[i]].rson;
return ask(mid + 1, r, k - sum);
}
}
int preask(int s, int t, int u, int w, int k) {
qtc = qsc = quc = qwc = 0;
for (int i = s; i; i -= -i & i) qs[++qsc] = root[i];
for (int i = t; i; i -= -i & i) qt[++qtc] = root[i];
for (int i = u; i; i -= -i & i) qu[++quc] = root[i];
for (int i = w; i; i -= -i & i) qw[++qwc] = root[i];
return ask(0, sz, k);
}
} bit;
int n, m, _, k;
int h[N], ne[N << 1], to[N << 1], tot;
int op[N], x[N], y[N], a[N], df;
PII dfn[N];
void add(int u, int v) { ne[++tot] = h[u]; to[h[u] = tot] = v;}
void dfs(int x, int f) {
dfn[x].fi = ++df;
for (int i = h[x]; i; i = ne[i]) {
int y = to[i];
if (y == f) continue;
dfs(y, x);
} dfn[x].se = df;
}
int main() {//op==0,表a[x]修改为y,其他表示求(x,y)简单路径第op大
IOS; cin >> n >> m; VI c;
rep (i, 1, n) cin >> a[i], c.pb(a[i]);
rep (i, 2, n) {
int u, v; cin >> u >> v;
add(u, v); add(v, u);
}
rep (i, 1, m) {
cin >> op[i] >> x[i] >> y[i];
if (!op[i]) c.pb(y[i]);
}
sort(all(c)); c.erase(unique(all(c)), c.end());
rep (i, 1, n) a[i] = lower_bound(all(c), a[i]) - c.begin();
rep (i, 1, m) if (!op[i]) y[i] = lower_bound(all(c), y[i]) - c.begin();
ST.init(n, h, ne, to); ST.bfs(1);//st表,求lca的轮子
dfs(1, 0); bit.build(n, c.size(), a, dfn);
rep (i, 1, m) {
if (!op[i]) {
bit.change(dfn[x[i]].fi, a[x[i]], -1);
bit.change(dfn[x[i]].se + 1, a[x[i]], 1);
a[x[i]] = y[i];
bit.change(dfn[x[i]].fi, a[x[i]], 1);
bit.change(dfn[x[i]].se + 1, a[x[i]], -1);
} else {
int k = ST.dist(x[i], y[i]) + 2 - op[i], lca = ST.lca(x[i], y[i]);
if (k <= 0) cout << "invalid request!\n";
else cout << c[bit.preask(dfn[x[i]].fi, dfn[y[i]].fi,
dfn[lca].fi, dfn[ST.f[lca][0]].fi, k)] << '\n';
}
}
return 0;
}
可持续化01trie
1、在序列末尾添加一个数 x。
2、找到一个位置 p,满足l≤p≤r,a[p~N]异或和 xor x 最大,输出这个最大值。
即 a[1]~a[n] 异或 (l-1r-1)a[1]a[i]与x的异或和最大
struct SustainableTrie {
const int N = 2e4 + 5, M = 31;
struct node {
int son[2], cnt;
int& operator [](const int x) { return son[x]; }
} tr[N * M];
int rt[N], tot;
void init() {
for (int i = 1; i <= tot; ++i) rt[i] = 0; tot = 0;
for (int i = 0; i < M; ++i) tr[0][i] = 0;
}
void insert(int& x, int y, int k) {
int p = x = ++tot; tr[p] = tr[y];
for (int i = M - 1; ~i; --i) {
int ch = k >> i & 1;
tr[p = tr[p][ch] = ++tot] = tr[y = tr[y][ch]];
++tr[p].cnt;
}
}
int ask(int x, int y, int k) {
int ans = 0;
for (int i = M - 1; ~i; --i) {
int ch = k >> i & 1;
if (tr[tr[x][!ch]].cnt - tr[tr[y][!ch]].cnt)
ans ^= 1ll << i, ch = !ch;
x = tr[x][ch], y = tr[y][ch];
} return ans;
}
} trie;
int n, m, _, k, s;
int main() {
IOS; cin >> n >> m; k = 1; trie.insert(trie.rt[k], trie.rt[0], 0);
rep (i, 1, n) cin >> _, ++k, trie.insert(trie.rt[k], trie.rt[k - 1], s ^= _);
rep (i, 1, m) {
char op[2]; cin >> op;
if (op[0] == 'A') cin>>_,++k,trie.insert(trie.rt[k],trie.rt[k - 1],s^=_);
else {
int l, r, x; cin >> l >> r >> x;
cout << trie.ask(trie.rt[r], trie.rt[l - 1], x ^ s) << '\n';
}
} return 0;
}
void work(int x) {
int tail = 0; dis[x] = { 0, 0 }; q[++tail] = x; v[f[x] = x] = 1;
/*for (auto& y : h[x]) if (!v[y.first] && y.second <= k)
dis[q[++tail]=y.first]={y.second,1},dfs(y.first,x,f[y.first]=y.first,tail);
sort(q + 1, q + tail + 1, [&](int a, int b) { return dis[a] < dis[b]; });
for (int l = 1, r = 1; l <= tail; ++l) {
while (l < r - 1 && dis[q[l]].first + dis[q[r - 1]].first >= k) --r;
while (r < tail && dis[q[l]].first + dis[q[r]].first < k) ++r;
for (; r <= tail && dis[q[l]].first + dis[q[r]].first == k; ++r)
if (f[q[l]] ^ f[q[r]]) ans = min(ans, dis[q[l]].second + dis[q[r]].second);
}遍历完孩子在计算*/
unordered_map<int, int> st; st[0] = 0;//st可换成桶,循环完把更新过的位置重新设max即可
for (auto& y : h[x]) if (!v[y.first] && y.second <= k) {
dis[q[++tail] = y.first] = { y.second, 1 }; q[tail = 1] = y.first;
dfs(y.first, x, f[y.first] = y.first, tail);
for (int i = 1; i <= tail; ++i) if (st.count(k - dis[q[i]].first))
ans = min(ans, st[k - dis[q[i]].first] + dis[q[i]].second);
for (int i = 1; i <= tail; ++i) {
auto it = st.find(dis[q[i]].first);
if (it == st.end()) st.insert(dis[q[i]]);
else it->second = min(it->second, dis[q[i]].second);
}
} //直接一边遍历一便计算, 省去f[]数组
gravity(x, -1, n); mxsz = n;
for(auto&y:h[x])if(!v[y.first])gravity(y.first,-1,sz[y.first]),work(gra),mxsz=n;
}
int main() {
IOS; cin >> n >> k; mxsz = ans = n;
for (int i = 1; i < n; ++i) {
int u, v, c; cin >> u >> v >> c;
h[u].push_back({ v, c }); h[v].push_back({ u, c });
}
gravity(0, -1, n); work(gra); cout << (ans ^ n ? ans : -1);
return 0;
}
点分治
1.先找当前子树的重心, 别走出当前子树
2.在把重心当成当前子树的根, 开始计算
3.对当前子树根节点(重心)的儿子节点进行1~3
带权树, 求简单路径长度为k, 所包含最少边的数量
#pragma GCC optimize(2)
int n, k, gra, sz[N], mxsz, q[N], f[N], ans;
bool v[N];
pair<int, int> dis[N];
vector<pair<int, int>> h[N];
void gravity(int x, int fa, int s) {
sz[x] = 1; int mx = 0;
for (auto& y : h[x]) if (y.first != fa && !v[y.first])
gravity(y.first, x, s), sz[x] += sz[y.first], mx = max(mx, sz[y.first]);
if ((mx = max(mx, s - sz[x])) < mxsz) gra = x, mxsz = mx;
}
void dfs(int x, int fa, int id, int& tail) {
for (auto& y : h[x]) if (y.first != fa && !v[y.first]) {
f[y.first] = id; dis[q[++tail] = y.first].second = dis[x].second + 1;
dis[y.first].first = dis[x].first + y.second;
if (dis[y.first].first < k) dfs(y.first, x, id, tail);
}
}
cdq 分治
三维偏序
第一行包含两个整数n和m,在刚开始时,Ayu已经知道有n个点可能埋着天使玩偶,接下来Ayu要进行m次操作。
接下来n行,每行两个非负整数xi,yi,表示初始n个点的坐标。
再接下来m行,每行三个非负整数 t,x,y 。
如果t=1,表示Ayu又回忆起了一个可能埋着玩偶的点(x,y)。
如果t=2,表示Ayu询问如果她在坐标(x,y),那么在已经回忆出的点里,离她最近的那个点有多远(曼哈顿)。
这题异常卡常, 开氧气, 最好再来快读
直接拆只考虑 \(|x-x_i|+|y-y_i|=(x+y)-(x_i+y_i)\)
旋转四次,cdq分治即可
直接拿时间轴当一维(直接有序)
二维偏序 x轴, 树状维护 y轴信息(还不用离散化)
记得树状恢复的技巧
#pragma GCC optimize(2)
template<class T1, class T2> bool umin(T1& a, T2 b) { return a > b?(a = b, true):false; }
template<class T1, class T2> bool umax(T1& a, T2 b) { return a < b?(a = b, true):false; }
template<class T> void clear(T& a) { T().swap(a); }
const int N = 5e5 + 5, M = 1e6 + 5;
int n, m, _, k;
struct node { int op, x, y, t; } q[N << 1], a[N << 1], b[N << 1];
int tot, ans[N << 1], c[M], mxx, mxy;
void add(int x, int k) { for (; x <= mxy; x += -x & x) umax(c[x], k); }
int ask(int x){int ans=0;for(;x;x-=-x&x) umax(ans,c[x]); return ans?ans:-1e7; }
void cl(int x) { for (; x <= mxy; x += -x & x) c[x] = 0; }
void init(int k) {
int x = 0, y = 0; tot = 0;
rep(i, 1, n + m) {
if (k == 1) q[i].x = mxx - q[i].x + 1;
else if (k == 2) q[i].y = mxy - q[i].y + 1;
if (q[i].op == 2) umax(x, q[i].x), umax(y, q[i].y);
}
rep(i, 1, n + m) if (q[i].x <= x && q[i].y <= y) a[++tot] = q[i];
}
void cdq(int l, int r) {
if (l == r) return;
int mid = l + r >> 1;
cdq(l, mid); cdq(mid + 1, r);
int x = l, y = mid + 1, z = l;
for (; y <= r; b[z++] = a[y++]) {
for (; x <= mid && a[x].x <= a[y].x; b[z++] = a[x++])
if (a[x].op == 1) add(a[x].y, a[x].x + a[x].y);
if (a[y].op == 2) umin(ans[a[y].t], a[y].y + a[y].x - ask(a[y].y));
}
rep (i, l, x - 1) if (a[i].op == 1) cl(a[i].y);
rep (i, x, mid) b[z++] = a[i];
rep (i, l, r) a[i] = b[i];
}
int main() {
IOS; cin >> n >> m; VI idx;
rep(i, 1, n) {
cin >> q[i].x >> q[i].y, q[i].t = i; q[i].op = 1;
umax(mxx, ++q[i].x); umax(mxy, ++q[i].y);
}
rep(i, n + 1, n + m) {
cin >> q[i].op >> q[i].x >> q[i].y; q[i].t = i;
umax(mxx, ++q[i].x); umax(mxy, ++q[i].y); ans[i] = 1e9;
if (q[i].op == 2) idx.pb(q[i].t);
}
init(0); cdq(1, tot); init(1); cdq(1, tot);
init(2); cdq(1, tot); init(1); cdq(1, tot);
for (auto i : idx) cout << ans[i] << '\n';
return 0;
}
整体二分
一个环,分成m个区间,每个分别属于一个国家,k个运算,使得[l,r] ([l,m],[1,r])加x,
求每个国家达到自己要求的a[i]最少要经过几次运算(运算顺序不可改),不存在NIE
可用区间树状,也可以用差分(区间修改,单点询问), 板子用的前面的区间改查
struct node { ll op, l, r, id; } q[N << 1], qu[N << 1];
int n, m, _, k;
ll ans[N], c[2][N];
VI a[N];
void solve(int l, int r, int ql, int qr) {
if (ql > qr) return;
if (l == r) {
rep(i, ql, qr) if (q[i].op == 0) ans[q[i].id] = l;
return;
}
int mid = l + r >> 1, nl = ql - 1, nr = 0;
rep(i, ql, qr)
if (q[i].op) {
if (q[i].id > mid) { qu[++nr] = q[i]; continue; }
if (q[i].l <= q[i].r) add(q[i].l, q[i].r, q[i].op);
else add(q[i].l, m, q[i].op), add(1, q[i].r, q[i].op);
//add(m + 1, -q[i].op) m+1并不会执行, 就省略了
q[++nl] = q[i];
} else {
__int128 cur = 0;//3e5*3e5*1e9 爆ll
for (auto j : a[q[i].id]) cur += ask(j, j);
if (cur >= q[i].l) q[++nl] = q[i];
else q[i].l -= cur, qu[++nr] = q[i];
}
rep(i, ql, nl) if (q[i].op)
if (q[i].l <= q[i].r) add(q[i].l, q[i].r, -q[i].op);
else add(q[i].l, m, -q[i].op), add(1, q[i].r, -q[i].op);
rep(i, 1, nr) q[i + nl] = qu[i];
solve(l, mid, ql, nl); solve(mid + 1, r, nl + 1, qr);
}
int main() {
IOS; cin >> n >> m;
rep(i, 1, m) cin >> _, a[_].pb(i);
rep(i, 1, n) cin >> qu[i].l, qu[i].op = 0, qu[i].id = i;
cin >> k;
rep(i, 1, k) cin >> q[i].l >> q[i].r >> q[i].op, q[i].id = i;
++k; q[k] = { (int)2e9, 1, m, k };
rep(i, 1, n) q[k + i] = qu[i];
solve(1, k, 1, k + n);
rep(i, 1, n) if (ans[i] != k) cout << ans[i] << '\n'; else cout << "NIE\n";
return 0;
}
可持续化并查集
ll n,m;
struct Lasting_Segment_Tree {
struct node { ll lson, rson fa, dep; } t[MAXN << 5 | 1];
ll cnt;
Lasting_Segment_Tree() { cnt=0; }
#define rt t[num]
void build(ll& num, un l = 1, un r = n) {
num=++cnt;
if (l==r)rt.fa=l,rt.dep=1;
else {
un mid=(l+r)>>1;
build(rt.lson,l,mid);
build(rt.rson,mid+1,r);
}
}
void modify(ll& num, ll pre, un pos, ll fa, un l = 1, un r = n) {
//现在节点是num,前一个版本的现在节点是pre,要将pos位置的fa改为fa
num = ++cnt; rt=t[pre];
if(l == r) { rt.fa=fa; return;}
un mid=(l+r)>>1;
if(pos<=mid)modify(rt.lson,t[pre].lson,pos,fa,l,mid);
else modify(rt.rson,t[pre].rson,pos,fa,mid+1,r);
}
void add(ll num, un pos, un l = 1, un r = n) {
//现在节点是num,要讲pos位置深度+1
if(l==r) { ++rt.dep; return;}
un mid=(l+r)>>1;
if(pos<=mid)add(rt.lson,pos,l,mid);
else add(rt.rson,pos,mid+1,r);
}
pll Query(ll num,un pos,un l = 1,un r = n) {
//返回pos位置的fa和dep
if (l == r)return pll(rt.fa,rt.dep);
un mid = (l + r) >> 1;
if(pos<=mid)return Query(rt.lson,pos,l,mid);
else return Query(rt.rson,pos,mid+1,r);
}
}sgt;
ll root[MAXN];//每个版本的根节点
pll find(ll w,ll x) {
//找第w个版本下x的根和深度
pll tmp=sgt.Query(root[w],x);
if (tmp.first == x)return tmp;
return find(w, tmp.first);//不能路径压缩!
}
int main() {
n = read(), m = read(); ll lastans=0;
sgt.build(root[0]);
for(ll i = 1, op = read(); i <= m; ++i) {
root[i] = root[i-1];//复制
if(op==1) {
pll x, y;
x = find(i, read() ^ lastans); y = find(i, read() ^ lastans);
if (x.first == y.first) continue;
if (x.second > y.second) std::swap(x,y);
sgt.modify(root[i], root[i-1], x.first, y.first);
if (x.second == y.second) sgt.add(root[i], y.first);
} else if(op == 2) root[i] = root[read() ^ lastans];
else {
ll x = read() ^ lastans, y = read() ^ lastans;
lastans = (find(i, x).first == find(i, y).first);
printf("%d\n", lastans);
}
}
return 0;
}
splay
struct Splay {
static const int N = 1e5 + 5;
struct Node {
int fa, val, siz, cnt, l, r, ch[2];
Node(int Fa = 0, int Val = 0, int Siz = 0, int L = 0, int R = 0):
fa(Fa), val(Val), siz(Siz), cnt(Siz) { ch[0] = L, ch[1] = R; }
} t[N];
int root, tot;
int getroot() { return t[root].val; }
bool son(int p) {return p == t[t[p].fa].ch[1]; }
int newNode(int fa=0,int v=0,int cnt=1){return t[++tot]=Node(fa,v,cnt),tot;}
void connect(int p, int q, int k) { if (p) t[p].fa = q; t[q].ch[k] = p; }
void push_up(int p) { t[p].siz = t[t[p].ch[0]].siz + t[t[p].ch[1]].siz + t[p].cnt; }
void rotate(int p) {
//if (root == p) return;
int f = t[p].fa, g = t[f].fa; bool q1 = son(p), q2 = son(f);
connect(t[p].ch[q1 ^ 1], f, q1); connect(f, p, q1 ^ 1);
connect(p, g, q2); push_up(f); push_up(p);
}
void splay(int p, int q) {
while (t[p].fa != q) {
int f = t[p].fa, g = t[f].fa;
if (g != q) rotate(son(p) ^ son(f) ? p : f);
rotate(p);
} if (q == 0) root = p;
}
void insert(int x) {
int p = root, fa = 0;
for (; p && t[p].val != x; fa = p, p = t[p].ch[x > t[p].val]);
if (p) ++t[p].cnt;
else { p = newNode(fa, x); if (fa) t[fa].ch[x > t[fa].val] = p; }
splay(p, 0);
}
void find(int x) {
if (!root) return; int p = root;
for (; t[p].val != x && t[p].ch[x > t[p].val]; p = t[p].ch[x > t[p].val]);
splay(p, 0);
}
int next(int x, bool f) { //f=1后继,0前驱
find(x); int p = root;
if (f && t[p].val > x) return p;
if (!f && t[p].val < x) return p;
for (p = t[p].ch[f]; t[p].ch[f ^ 1]; p = t[p].ch[f ^ 1]);
return p;
}
void detel(int x) {
int pre = next(x, 0), nex = next(x, 1);
splay(pre, 0); splay(nex, pre);
int del = t[nex].ch[0];
if (t[del].cnt > 1) --t[del].cnt, splay(del, 0);
else t[nex].ch[0] = 0;
}
int kth(int k) {
if (!root || t[root].siz < k) return -1;
int p = root;
while (1) {
if (t[t[p].ch[0]].siz + t[p].cnt < k)
k -= t[t[p].ch[0]].siz + t[p].cnt, p = t[p].ch[1];
else
if (t[t[p].ch[0]].siz >= k) p = t[p].ch[0]; else return t[p].val;
}
}
int getrk(int x){return find(x), t[root].val != x ? -1 : t[t[root].ch[0]].siz + 1;}
} T;
int main() {
IOS; cin >> n;
T.insert(2e9); T.insert(-2e9);
rep (i, 1, n) {
int x, op; cin >> op >> x;
if (op == 1) T.insert(x);
else if (op == 2) T.detel(x);
else if (op == 3) cout << T.getrk(x) - 1 << '\n';
else if (op == 4) cout << T.kth(x + 1) << '\n';
else if (op == 5) cout << T.t[T.next(x, 0)].val << '\n';
else cout << T.t[T.next(x, 1)].val << '\n';
}
return 0;
}
FHQ 平衡树
纯板子
struct FHQ {
static const int N = 1e5 + 5;
struct Node {
int ch[2], val, pri, siz;
Node (int S = 0, int V = 0, int P = 0, int l = 0, int r = 0) :
siz(S), val(V), pri(P) { ch[0] = l, ch[1] = r; }
int& operator [](const int k) { return ch[k]; }
} tr[N];
FHQ() { srand((unsigned)time(NULL)); }
int tot, x, y, z, root;
int newNode (int v) { tr[++tot] = Node(1, v, rand()); return tot; }
void update(int x) { tr[x].siz = 1 + tr[tr[x][0]].siz + tr[tr[x][1]].siz; }
int merge(int x, int y) {
if (!x || !y) return x + y;
if (tr[x].pri<tr[y].pri) {tr[x][1] = merge(tr[x][1], y); update(x); return x;}
else { tr[y][0] = merge(x, tr[y][0]); update(y); return y; }
}
void split_v(int p, int k, int &x, int& y) {
if (!p) x = y = 0;
else {
if (tr[p].val <= k) x = p, split_v(tr[p][1], k, tr[p][1], y);
else y = p, split_v(tr[p][0], k, x, tr[p][0]);
update(p);
}
}
void split_k(int p, int k, int &x, int &y) {
if (!p) x = y = 0;
else {
if (k <= tr[tr[p][0]].siz) y = p, split_k(tr[p][0], k, x, tr[p][0]);
else x = p, split_k(tr[p][1], k - tr[tr[p][0]].siz - 1, tr[p][1], y);
update(p);
}
}
int kth(int p, int k) {
while (1) {
if (k <= tr[tr[p][0]].siz) p = tr[p][0];
else if (k == tr[tr[p][0]].siz + 1) return p;
else k -= tr[tr[p][0]].siz + 1, p = tr[p][1];
}
}
int getrk(int val) {
split_v(root, val - 1, x, y);
int ans = tr[x].siz + 1;
return root = merge(x, y), ans;
}
void add_v(int pos, int val) {
split_v(root, pos, x, y);
root = merge(merge(x, newNode(val)), y);
}
void del_v(int val) {
split_v(root, val, x, z);
split_v(x, val - 1, x, y);
y = merge(tr[y][0], tr[y][1]);
root = merge(merge(x, y), z);
}
void del_k(int k) {
split_k(root, k, x, z);
split_k(x, k - 1, x, y);
y = merge(tr[y][0], tr[y][1]);
root = merge(merge(x, y), z);
}
int pre(int val) {
split_v(root, val - 1, x, y);
int ans = tr[kth(x, tr[x].siz)].val;
return root = merge(x, y), ans;
}
int nxt(int val) {
split_v(root, val, x, y);
int ans = tr[kth(y, 1)].val;
return root = merge(x, y), ans;
}
} T;
区间反转/轮换
struct FHQ {
static const int N = 5e5 + 5;
struct Node {
int ch[2], pri, siz; ll tag, val, miv; bool rever;
int& operator [](const int k) { return ch[k]; }
} tr[N];
FHQ () { srand((unsigned)time(NULL)); }
int tot, x, y, z, root;
int newNode (int val) {
tr[++tot].val = val; tr[tot].pri = rand(); tr[tot].tag = 0;
tr[tot].rever = 0; tr[tot][0] = tr[tot][1] = 0;
return tr[tot].miv = val, tr[tot].siz = 1, tot;
}
void push_up(int p) {
tr[p].siz = 1 + tr[tr[p][0]].siz + tr[tr[p][1]].siz; tr[p].miv = tr[p].val;
if (tr[p][0]) umin(tr[p].miv, tr[tr[p][0]].miv);
if (tr[p][1]) umin(tr[p].miv, tr[tr[p][1]].miv);
}
void push_down(int p) {
tr[tr[p][0]].tag += tr[p].tag; tr[tr[p][1]].tag += tr[p].tag;
tr[tr[p][0]].val += tr[p].tag; tr[tr[p][1]].val += tr[p].tag;
tr[tr[p][0]].miv += tr[p].tag; tr[tr[p][1]].miv += tr[p].tag;
if (tr[p].rever)
tr[tr[p][0]].rever^=1, tr[tr[p][1]].rever^=1, swap(tr[p][0],tr[p][1]);
tr[p].rever = tr[p].tag = 0;
}
int merge(int x, int y) {
if (!x || !y) return x + y; push_down(x); push_down(y);
if (tr[x].pri < tr[y].pri) tr[x][1] = merge(tr[x][1], y);
else tr[y][0] = merge(x, tr[y][0]), swap(x, y);
push_up(x); return x;
}
void split(int p, int k, int &x, int &y) {
if (!p) x = y = 0;
else {
push_down(p);
if (k <= tr[tr[p][0]].siz) y = p, split(tr[p][0], k, x, tr[p][0]);
else x = p, split(tr[p][1], k - tr[tr[p][0]].siz - 1, tr[p][1], y);
push_up(p);
}
}
void add(int pos, int val) {
split(root, pos, x, y); root = merge(merge(x, newNode(val)), y);
}
void del(int k){split(root,k,x,z);split(x,k-1,x,y);root=merge(x, z);}
void res(int l, int r) { //反转l, r
split(root, r, x, z); split(x, l - 1, x, y);
tr[y].rever ^= 1; root = merge(merge(x, y), z);
}
void rev(int l, int r, ll t) { //[l,r] t次轮换
t %= (r - l + 1); if (!t) return;
split(root,r,root,z);split(root,r-t,root,y);split(root,l-1,root,x);
root = merge(merge(root, y), merge(x, z));
}
void change(int l, int r, ll k) { //[l,r] 区间+k
split(root, r, x, z); split(x, l - 1, x, y);
tr[y].tag += k, tr[y].val += k, tr[y].miv += k; root = merge(merge(x, y), z);
}
ll minval(int l, int r) { //[l,r]最小值
split(root, r, x, z); split(x, l - 1, x, y);
ll ans = tr[y].miv; return root = merge(merge(x, y), z), ans;
}
} T;
FHQ启发式合并
struct FHQ {
static const int N = 4e5 + 5;
FHQ() { srand(time(0)); }
struct Node {
int ch[2], val, pri; ll siz, cnt;
int& operator [](int k) { return ch[k]; }
} tr[N];
int rt[N], tot;
void push_up(int p){tr[p].siz = tr[tr[p][0]].siz + tr[tr[p][1]].siz + tr[p].cnt;}
int newNode(int v, ll c) {
tr[++tot].val = v; tr[tot].siz = tr[tot].cnt = c;
return tr[tot][0] = tr[tot][1] = 0; tr[tot].pri = rand(), tot;
}
void split(int p, int k, int& x, int& y) {
if (!p) x = y = 0;
else {
if (tr[p].val <= k) x = p, split(tr[p][1], k, tr[p][1], y);
else y = p, split(tr[p][0], k, x, tr[p][0]);
push_up(p);
}
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].pri < tr[y].pri) tr[x][1] = merge(tr[x][1], y);
else tr[y][0] = merge(x, tr[y][0]), swap(x, y);
push_up(x); return x;
}
int split(int& p, int l, int r) {
int x,y,z;split(p,r,x,z);split(x,l-1,x,y);p=merge(x,z);return y;
}
int unit(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].pri > tr[y].pri) swap(x, y);
int a, b, c; split(y, tr[x].val, a, b); split(a, tr[x].val - 1, a, c);
if (c) tr[x].cnt += tr[c].cnt, tr[x].siz += tr[c].siz;
tr[x][0] = unit(tr[x][0], a); tr[x][1] = unit(tr[x][1], b);
push_up(x); return x;
}
void insert(int& p, int v, ll c) {
if (!c) return;
int x, y, z; split(p, v, x, z); split(x, v - 1, x, y);
if (!y) y = newNode(v, c);
else tr[y].cnt += c, tr[y].siz += c;
p = merge(merge(x, y), z);
}
ll ask(int& p, int l, int r) {
int x, y, z; split(p, r, x, z); split(x, l - 1, x, y);
ll ans = tr[y].siz; p = merge(merge(x, y), z);return ans;
}
int kth(int p, ll k) {
if (k > tr[p].siz) return -1;
while (1)
if (tr[tr[p][0]].siz >= k) p = tr[p][0];
else if (tr[tr[p][0]].siz + tr[p].cnt >= k) return tr[p].val;
else k -= tr[tr[p][0]].siz + tr[p].cnt, p = tr[p][1];
}
} T;
int n, m, _, k, tot = 1;
void solve0() { //把p中值域[x,y]分出建新树
int p, x, y; cin >> p >> x >> y;
T.rt[++tot] = T.split(T.rt[p], x, y);
}
void solve1() { //合并y和x T.rt[x] = T.rt[y]
int x, y; cin >> x >> y;
T.rt[x] = T.unit(T.rt[x], T.rt[y]);
}
void solve2() { //在树p加入y个x
int p, x, y; cin >> p >> x >> y;
T.insert(T.rt[p], y, x);
}
ll solve3() { //求树p值域[x,y]数的个数
int p, x, y; cin >> p >> x >> y;
return T.ask(T.rt[p], x, y);
}
int solve4() { //求树p中第k小
ll p, k; cin >> p >> k;
return T.kth(T.rt[p], k);
}
数学
素数
米勒拉宾判素数
bool millerRabbin(int n) {
if (n < 3) return n == 2;
int a = n - 1, b = 0;
while (a % 2 == 0) a /= 2, ++b;
srand(time(0));
//test_time为测试次数,建议设为不小于8,也不应太大
for (int i = 1, j; i <= test_time; ++i) {
int x = rand() % (n - 2) + 2, v = qpow(x, a, n);//x^a%n
if (v == 1 || v == n - 1) continue;
for (j = 0; j < b; ++j) {
v = (long long)v * v % n;
if (v == n - 1) break;
}
if (j >= b) return 0;
}
return 1;
}
反素数
如果某个正整数n满足如下条件,则称为是反素数:任何小于n的正数的约数个数都小于n的约数个数,即因子最多的数(因子数相同取最小的数)
因子数为n的最小数
ull p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53}, ans;
int n;
// depth: 当前在枚举第几个素数。num: 当前因子数。
// temp: 当前因子数量为num的时候的数值。
// up:上一个素数的幂,这次应该小于等于这个幂次
void dfs(int depth, ull temp, int num, int up) {
if (num > n || depth > 15) return;
if (num == n && ans > temp) { ans = temp; return; }
for (int i = 1; i <= up; ++i) {
if (temp * p[depth] >= ans) break;
dfs(depth + 1, temp *= p[depth], num * (i + 1), i);
}
}
int main() {
while (scanf("%d", &n) != EOF) {
ans = ~0ULL; dfs(0, 1, 1, 64);
printf("%llu\n", ans);
}
return 0;
}
小于n因子数最大的数
ull p[16] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53}, ans;
int ans_num, n; //ans为n以内的最大反素数(会持续更新),ans_sum为ans的因子数。
void dfs(int depth, ull temp, int num, int up) {
if (depth > 15 || temp > n) return;
if (num > ans_num) { ans = temp; ans_num = num; }
if (num == ans_num && ans > temp) ans = temp;
for (int i = 1; i <= up; ++i) {
if (temp * p[depth] > n) break;
dfs(depth + 1, temp *= p[depth], num * (i + 1), i);
}
}
int main() {
while (scanf("%d", &n) != EOF) {
ans_num = 0; dfs(0, 1, 1, 64);
printf("%llu\n", ans);
}
return 0;
}
公约数/倍数
gcd
gcd(a, b) = gcd(b, a-b) = gcd(a, a-b) = gcd(b, a % b) = gcd(a+b, a)
gcd(a, b, c, d, .., z) = gcd(a, b - a, c, d, ..., z - a)
gcd(a[i], a[i + 1], ..., a[j]) = gcd(a[i], a[i + 1] - a[i], ..., a[j] - a[j - 1])
这样对于数组区间修改相当于在差分数组上单点修改, 可用线段树维护区间gcd, a[i]用树状维护
lcm
lcm[a, b] = c * b / gcd(a, b)
扩展欧几里得
ll exgcd(ll a, ll b, ll& x, ll& y) {
if (b == 0) { x = 1, y = 0; return a; }
ll d = exgcd(b, a % b, y, x); y -= a / b * x;
return d;
}
欧拉函数
求n的欧拉函数
int getphi(int n) {
int ans = n;
rep (i, 2, n / i) if (n % i == 0) {
ans = ans / i * (i - 1);
while (n % i == 0) n /= i;
}
if (n > 1) ans = ans / n * (n - 1);
return ans;
}
筛法
Euler筛法(pri, phi, mu)
int v[N], pri[N], phi[N], mu[N], tot;
void init(int n) {
memset(v, 0, sizeof v); tot = 0; mu[1] = 1;
rep (i, 2, n) {
if (v[i] == 0) mu[v[i] = pri[++tot] = i] = -1, phi[i] = i - 1;
rep (j, 1, tot) {
if (pri[j] > v[i] || pri[j] > n / i) break;
v[i * pri[j]] = pri[j]; //每个数的最小质因子
phi[i * pri[j]] = phi[i] * (pri[i] - (i % pri[j] != 0));
mu[i * pri[j]] = i % pri[j] ? -mu[i] : 0;
}
}
}
Euler筛法(约数个数)
约数个数定理: 若\(n=\Pi^m_{i=1}p_i^{c_i}\), 则\(cnt=\Pi^m_{i=1}c_i+1\)
\(d_i=\Pi^m_{i=1}c_i+1\) 是积性函数, 自然可以用Euler筛法
int v[N], pri[N], d[N], num[N], tot; //num最小质数个数
void init(int n) {
memset(v, 0, sizeof v); tot = 0; d[1] = 1;
rep (i, 2, n) {
if (v[i] == 0) v[i] = pri[++tot] = i, d[i] = 2, num[i] = 1;
rep (j, 1, tot) {
if (pri[j] > v[i] || pri[j] > n / i) break;
int cur = i * pri[j]; v[cur] = pri[j]; //每个数的最小质因子
num[cur] = i % pri[j] ? 1 : num[i] + 1;
d[cur] = i % pri[j] ? d[i] * 2 : d[i] / num[cur] * (num[cur] + 1);
}
}
}
Euler筛法(约数和)
int v[N], pri[N], g[N], f[N], tot;//f[i]i约数和, g[i]i的最小质因子的等比数列和,必要开ll
void init(int n) {
memset(v, 0, sizeof v); tot = 0; f[1] = g[1] = 1;
rep (i, 2, n) {
if (v[i] == 0) v[i] = pri[++tot] = i, f[i] = g[i] = i + 1;
rep (j, 1, tot) {
if (pri[j] > v[i] || pri[j] > n / i) break;
int cur = i * pri[j]; v[cur] = pri[j]; //每个数的最小质因子
g[cur] = i % pri[j] ? 1 + pri[j] : g[i] * pri[j] + 1;
f[cur] = i % pri[j] ? f[i] * f[pri[j]] : f[i] / g[i] * g[cur];
}
}
rep (i, 1, n) f[i] = (f[i - 1] + f[i]) % mod;
}
欧拉定理 && 费马小定理 && 裴蜀定理
费马小定理
若p为素数, \(gcd(p, a)=1\), 则\(a^{p-1}\equiv 1(mod \ p)\)
对任意整数 \(a^p\equiv a(mod \ p)\)
欧拉定理
若\(gcd(a,m)=1\), 则\(a^{\varphi (m)}\equiv 1\ (mod \ m)\)
扩展欧拉定理
\(a^b(mod \ p) \equiv \left\{\begin{matrix} a^{b \ mod \ \varphi (p)}, & gcd(a,p)=1\\ a^b, & gcd(a,p)\neq 1,b\leqslant \varphi (p)\\ a^{b \ mod \ \varphi (p) + \varphi (p)}, & gcd(a,p) \neq 1,b \geqslant \varphi(p) \end{matrix}\right.\)
裴蜀定理
任意x,y不全为零, 存在\(ax+by=gcd(a,b)\)
乘法逆元
单个逆元
快速幂\(a^{-1}(mod \ p) \equiv a^{p-2}(mod \ p)\)
线性逆元
inv[0] = inv[1] = 1;
rep (i, 2, n) inv[i] = (ll)(mod - mod / i) * inv[mod % i] % mod;
线性求任意 n 个数的逆元
s[0] = 1;
rep (i, 1, n) s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2);
per (i, n, 1) sv[i - 1] = sv[i] * a[i] % p;
rep (i, 1, n) inv[i] = sv[i] * s[i - 1] % p;
线性同余方程
\(ax\equiv c(mod \ b)\)等价于\(ax+by=c\)
当\(gcd(a,b)|c\)时有解, \(ax_0+by_0=gcd(x,y)\)
通解为\(x=\frac{c}{gcd(a,b)}x_0+k\frac{b}{gcd(a,b)}, \ y=\frac{c}{gcd(a,b)}y_0-k\frac{a}{gcd(a,b)}\)
中国剩余定理
模数两两互质
ll CRT(ll a[], ll m[], int n) {
ll t = 1, ans = 0;
for (int i = 0; i < n; ++i) t *= m[i];
for (int i = 0; i < n; ++i) {
ll cur = t / m[i], x, y; exgcd(cur, m[i], x, y);
ans = (ans + a[i] * cur % t * x % t) % t;
}
return (ans + t) % t;
}
模数不互质
\(\left\{\begin{matrix} x \equiv a_1 (mod \ m_1)\\ x \equiv a_2 (mod \ m_2)\\ ...\\ x \equiv a_k (mod \ m_k)\end{matrix}\right.\)
\(x=m_1p+a_1=m_2q+a_2 \ \ p,q∈Z\), 则\(m_1p-m_2q=a_2-a_1\)
由裴蜀定理得\(a_2-a_1=0(mod \ gcd(m_1,m_2))\)否则无解
则合并两项得出通解\(x \equiv m_1p+a_1(mod \ lcm(m_1,m_2))\), 不断合并求出x
ll mod(ll x, ll p) { return (x % p + p) % p; }
ll CRT(ll a[], ll m[], int n) {
for (int i = 2; i <= n; ++i) {
ll k1, k2, d = exgcd(m[1], m[i], k1, k2);
ll c = mod(a[i] - a[1], m[i]);
if (c % d) return -1;
ll p = m[i] / d; c = c / d % p; k1 = mod(k1 * c, p);
a[1] += m[1] * k1; m[1] = m[1] / d * m[i];
}
return a[1];
}
baby-step giant-step
\(a^x\equiv b(mod \ p), a\perp p\)
ll baby_step_giant_step(ll a, ll b, ll p) {
unordered_map<ll, ll> st; b %= p;
int t = sqrt(p - 1) + 1; ll cur = 1;
for (int i = 0; i < t; ++i, cur = cur * a % p) st[b * cur % p] = i;
a = qpow(a, t, p); cur = a;
if (a == 0) return b == 0 ? 1 : -1;
for (int i = 1; i <= t; ++i, cur = cur * a % p) {
ll c = st.count(cur) ? st[cur] : -1;
if (c >= 0 && i * t - c >= 0) return i * t - c;
}
return -1;
}
\(a^x\equiv b(mod \ p)\)
具体地,设\(d_1=gcd(a,p)\)。如果\(d_1\nmid b\),则原方程无解。否则我们把方程同时除以\(b_1\),得到
\(\frac{a}{d_1}a^{x-1}\equiv \frac{b}{d_1}(mod \ \frac{p}{d_1})\)
如果\(a\)和\(p\)仍不互质就再除,设\(d_2=gcd(a,p)\)。如果\(d_2\nmid \frac{b}{d_1}\),则方程无解;否则同时除以\(d_2\)得到
\(\frac{a^2}{d_1d_2}a^{x-2}\equiv \frac{b}{d_1d_2}(mod \ \frac{p}{d_1d_2})\)
同理,这样不停的判断下去。直到\(a\perp \frac{p}{d_1d_2...d_k}\)。
记\(D=\prod_{i=1}^k d_i\),于是方程就变成了这样:\(\frac{a^k}{D}a^{x-k}\equiv \frac{b}{D}(mod \ \frac{p}{D})\)
由于\(a\perp\frac{p}{D}\),于是\(\frac{a^k}{D}\perp\frac{p}{D}\)推出。这样\(\frac{a^k}{D}\)就有逆元了,于是把它丢到方程右边,这就是一个普通的\(BSGS\)问题了,于是求解\(x-k\)后再加上\(k\)就是原方程的解啦。
注意,不排除解小于等于\(k\)的情况,所以在消因子之前做一下\(O(k)\)枚举,直接验证\(a^i\equiv b(mod \ p)\)
高斯消元
无模数(用double)
double a[N][N];
int gauss(int n, int m) {
int c = 1, r = 1;
for (int t = r; c < m && r <= n; ++c, t = r) {
rep (i, r + 1, n) if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
if (fabs(a[t][c]) < eps) continue;
if (t != r) rep (i, 1, m) swap(a[t][i], a[r][i]);
rep (i, c + 1, m) a[r][i] /= a[r][c]; a[r][c] = 1;
for (int i = r + 1; i <= n; a[i++][c] = 0) if (a[i][c])
rep (j, c + 1, m) a[i][j] -= a[i][c] * a[r][j];
++r;
}
rep (i, r, n) if (a[i][m]) return -1;
if (r < m) return 0;
per (i, m - 2, 1) rep (j, i + 1, m - 1) a[i][m] -= a[j][m] * a[i][j];
return 1;
}
有模数(用费马定理)
int a[N][N];
int gauss(int n, int m) {
int c = 1, r = 1;
for (int t = -1; c < m && r <= n; ++c, t = -1) {
rep (i, r, n) if (a[i][c]) { t = i; break; }
if (t == -1) continue;
if (t != r) swap(a[t], a[r]);
a[r][c] = qpow(a[r][c], mod - 2);
rep (i, c + 1, m) a[r][i] = (ll)a[r][i] * a[r][c] % mod; a[r][c] = 1;
for (int i = r + 1; i <= n; a[i++][c] = 0) if (a[i][c])
rep (j, c + 1, m) a[i][j]=((a[i][j] - a[r][j] * a[i][c])%mod+mod)%mod;
++r;
}
rep (i, r, n) if (a[i][m]) return -1;
if (r < m) return 0;
per (i, m - 1, 1) rep (j, i + 1, m - 1)
a[i][m] = ((a[i][m] - a[j][m] * a[i][j]) % mod + mod) % mod;
return 1;
}
异或消元
bitset<N> d[N];
ll gauss(int n, int m) {
int c = 1, r = 1;
for (int t = r; c <= m && r <= n; ++c, t = ++r) {
rep(i, r + 1, n) if (d[i][c]) t = i;
if (!d[t][c]) { --r; continue; }
if (t != r) swap(d[t], d[r]);
for (int i = r + 1; i <= n; d[i++][c] = 0) if (d[i][c])
d[i] ^= d[r];
}
return n - r + 1;
}
线性基
struct XXJ {
static const int N = 59;
ll g[N + 1]; bool zero = 0; vector<ll> a;
void init() { memset(g, 0, sizeof g); zero = 0; }
bool insert(ll x) {
per (i, N, 0) if (x >> i & 1)
if (!g[i]) {g[i] = x; return 1; } else x ^= g[i];
return zero = 1, 0;
}
bool isin(ll x) { per (i, N, 0) if (x >> i & 1) x ^= g[i]; return !x; }
ll max() { ll mx = 0; per (i, N, 0) umax(mx, mx ^ g[i]); return mx; }
ll min() { if (zero) return 0; rep (i, 0, N) if (g[i]) return g[i]; }
void build() {
vector<ll>().swap(a);
rep (i, 0, N) {
per (j, i - 1, 0) if (g[i] >> j & 1) g[i] ^= g[j];
if (g[i]) a.emplace_back(g[i]);
}
}
ll kth(ll k) {
ll ans = 0; k -= zero; if (k <= 0) return !k ? 0 : -1;
if (k >> a.size()) return -1;
rep (i, 0, a.size() - 1) if (k >> i & 1) ans ^= a[i];
return ans;
}
} xxj;
排列组合
多重集的组合数1
\(S=\{n_1*a_1,n_2*a_2,...,n_k*a_k\}\)选r个元素\((r \leqslant min(n_1))\), 组合数为\(S=C_{r+(k-1)}^{r-1}\)
多重集的组合数2
\(S=\{n_1*a_1,n_2*a_2,...,n_k*a_k\}\)选r个元素, 组合数为\(|\bigcap_i^k \sum S_i|=S-|\bigcup_i^k \sum \bar{S_i}|=\sum_iC_{r+k-1-(n_i+1)}^{k-1} - \sum_{i,j}C_{r+k-1-(n_i+1)-(n_j+1)}^{k-1}+..+(-1)^kC_{r-1-\sum_{i=k}^kn_i}^{k-1}\)
不相邻的排列
从1~n个数选k个数互不相邻, \(S=C_{n-k+1}^k\)
错排公式
\(f(n)=(n-1)(f(n-1)+f(n-2))=\left \lfloor n!/e+0.5 \right \rfloor\)
园排列
\(Q_n^r=\frac{A_n^r}{r}=\frac{n!}{r*(n-r)!}\)
Catlan
n个0, n个1, 任意前缀0个数大于1的个数, \(Cat_n = \frac{C^n_{2n}}{n+1}\)
n对括号匹配, n个数进出栈, n个节点构成二叉树, (0,0)->(n,m)除端点不接触y=x走法\(2Cat_{n-1}\)
Lucas定理
\(C_n^m=C_{n \ mod \ p}^{m \ mod \ p} * Lucas_{n/p}^{m/p} \ \ mod \ p\)
ll C(int n, int m, int k) {
return m>n?0:fac[k][n]*facinv[k][m]%mod[k]*facinv[k][n - m]%mod[k];
}
ll lucas(ll n, ll m, int k) {
return m?C(n%mod[k],m%mod[k],k)*lucas(n/mod[k],m/mod[k],k)%mod[k]:1;
}
莫比乌斯
miu[1] = 1;
rep (i, 2, n) {
if (!v[i]) prime[++tot] = i, miu[i] = -1;
for (int j = 1; prime[j] <= n / i && j <= tot; ++j) {
v[prime[j] * i] = 1;
if(i % prime[j] == 0) break;
else miu[i * prime[j]] = -miu[i];
}
}
进制任意转换
void work(int a, int b, string& s) {
int len = int(s.size());
VI cur(len), ans;
rep(i, 0, len - 1) cur[i] = s[i] - (s[i]<='9'?'0':s[i]<='Z'?'A'-9:'a'-35);
for (int i = 0; i < len;) {
rep (j, i + 1, len - 1) cur[j] += cur[j - 1] % b * a, cur[j - 1] /= b;
ans.pb(cur[len - 1] % m); cur[len - 1] /= m;
while (i < len && cur[i] == 0) ++i;
} s = "";
per(i,ans.size()-1,0)s+=char(ans[i]+(ans[i]<=9?'0':ans[i]<=35?'A'-9:'a'-35);
}
模拟退火
const double eps = 1e-3; //精度
const double start_T = 1000; //初始温度
const double rate = 0.98; //温度下降速率
int n, m, _, k;
struct point {
double x;
double y;
double z;
} p[N];
double dist(point a, point b) {
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z));
}
double solve() {
double T = start_T;
point ans_p = {0,0,0}; //初始点
double ans = 1e99; //预设一个较大值
while (T > eps) {
point maxd_p = p[1];
rep (i, 1, n)
if (dist(ans_p, p[i]) > dist(ans_p, maxd_p)) maxd_p = p[i];
//找到距离ans_p最远的点,maxd_p
ans = min(ans, dist(ans_p, maxd_p));
ans_p.x += (maxd_p.x - ans_p.x) * (T / start_T); //以一定概率靠近maxd_p
ans_p.y += (maxd_p.y - ans_p.y) * (T / start_T);
ans_p.z += (maxd_p.z - ans_p.z) * (T / start_T);
T *= rate;
}
return ans;
}
int main() {
IOS; cin >> n;
rep(i, 1, n) cin >> p[i].x >> p[i].y >> p[i].z;
cout << setiosflags(ios::fixed) << setprecision(8) << solve();
return 0;
}
__builtin函数 (__int128适用)
__builtin_popcount(x); // x二进制1的个数
__builtin_ffs(x); //比lowbit慢
__bultin_ctz(x); //x二进制末尾0的个数
__bultin_clz(x); //二进制前导0, 0未知
__builtin_parity(x);//x的二进制数中1的个数的奇偶性
大数
#define MAXN 9999 // MAXN 是一位中最大的数字
#define MAXSIZE 10024 // MAXSIZE 是位数
#define DLEN 4 // DLEN 记录压几位
struct Big {
int a[MAXSIZE], len;
bool flag; // 标记符号'-'
Big() { len = 1; memset(a, 0, sizeof a); flag = 0; }
Big(const int);
Big(const char*);
Big(const Big&);
Big& operator=(const Big&);
Big operator+(const Big&) const;
Big operator-(const Big&) const;
Big operator*(const Big&)const;
Big operator/(const int&) const;
Big operator^(const int&) const;
// TODO: Big 位运算;
int operator%(const int&) const;
// TODO: Big ^ Big;
bool operator<(const Big&) const;
bool operator<(const int& t) const;
inline void print() const;
};
Big::Big(const int b) {
int c, d = b; len = 0;
CLR(a); //memset(a,0,sizeof a);
while (d > MAXN) {
c = d - (d / (MAXN + 1) * (MAXN + 1));
d = d / (MAXN + 1); a[len++] = c;
}
a[len++] = d;
}
Big::Big(const char* s) {
int t, k, index, l; CLR(a);
l = strlen(s); len = l / DLEN;
if (l % DLEN) ++len;
index = 0;
for (int i = l - 1; i >= 0; i -= DLEN) {
t = 0; k = i - DLEN + 1;
if (k < 0) k = 0;
g(j, k, i) t = t * 10 + s[j] - '0';
a[index++] = t;
}
}
Big::Big(const Big& T) : len(T.len) {
CLR(a);
for (int i = 0; i < len; ++i) a[i] = T.a[i];
// TODO:重载此处?
}
Big& Big::operator=(const Big& T) {
CLR(a); len = T.len;
for (int i = 0; i < len; ++i) a[i] = T.a[i];
return *this;
}
Big Big::operator+(const Big& T) const {
Big t(*this);
int big = len;
if (T.len > len) big = T.len;
for (int i = 0; i < big; ++i) {
t.a[i] += T.a[i];
if (t.a[i] > MAXN) { ++t.a[i + 1]; t.a[i] -= MAXN + 1; }
}
if (t.a[big]) t.len = big + 1;
else t.len = big;
return t;
}
Big Big::operator-(const Big& T) const {
int big;
bool ctf;
Big t1, t2;
if (*this < T) {
t1 = T; t2 = *this; ctf = 1;
}
else { t1 = *this; t2 = T; ctf = 0; }
big = t1.len;
int j = 0;
for (int i = 0; i < big; ++i) {
if (t1.a[i] < t2.a[i]) {
j = i + 1;
while (t1.a[j] == 0) ++j;
--t1.a[j--];
// WTF?
while (j > i) t1.a[j--] += MAXN;
t1.a[i] += MAXN + 1 - t2.a[i];
}
else t1.a[i] -= t2.a[i];
}
t1.len = big;
while (t1.len > 1 && t1.a[t1.len - 1] == 0) { --t1.len; --big; }
if (ctf) t1.a[big - 1] = -t1.a[big - 1];
return t1;
}
Big Big::operator*(const Big& T) const {
Big res;
int up, te, tee;
for (int i = 0; i < len; ++i) {
up = 0;
for (int j = 0; j < T.len; ++j) {
te = a[i] * T.a[j] + res.a[i + j] + up;
if (te > MAXN) {
tee = te - te / (MAXN + 1) * (MAXN + 1);
up = te / (MAXN + 1);
res.a[i + j] = tee;
}
else { up = 0; res.a[i + j] = te; }
}
if (up) res.a[i + T.len] = up;
}
res.len = len + T.len;
while (res.len > 1 && res.a[res.len - 1] == 0) --res.len;
return res;
}
Big Big::operator/(const int& b) const {
Big res;
int down = 0;
gd(i, len - 1, 0) {
res.a[i] = (a[i] + down * (MAXN + 1) / b);
down = a[i] + down * (MAXN + 1) - res.a[i] * b;
}
res.len = len;
while (res.len > 1 && res.a[res.len - 1] == 0) --res.len;
return res;
}
int Big::operator%(const int& b) const {
int d = 0;
gd(i, len - 1, 0) d = (d * (MAXN + 1) % b + a[i]) % b;
return d;
}
Big Big::operator^(const int& n) const {
Big t(n), res(1);
int y = n;
while (y) {
if (y & 1) res = res * t;
t = t * t;
y >>= 1;
}
return res;
}
bool Big::operator<(const Big& T) const {
int ln;
if (len < T.len) return 233;
if (len == T.len) {
ln = len - 1;
while (ln >= 0 && a[ln] == T.a[ln]) --ln;
if (ln >= 0 && a[ln] < T.a[ln]) return 233;
return 0;
}
return 0;
}
inline bool Big::operator<(const int& t) const {
Big tee(t);
return *this < tee;
}
inline void Big::print() const {
printf("%d", a[len - 1]);
gd(i, len - 2, 0) { printf("%04d", a[i]); }
}
inline void print(Big s) { // s不要是引用,要不然你怎么print(a * b);
int len = s.len;
printf("%d", s.a[len - 1]);
gd(i, len - 2, 0) { printf("%04d", s.a[i]); }
}
char s[100024];
计算几何二维
const int N = 262144 + 3;
/*一:【准备工作】*/
#define int register int
const double eps = 1e-8, Pi = acos(-1.0);
inline int dcmp(double a) { return a < -eps ? -1 : (a > eps ? 1 : 0); }//处理精度
inline double Abs(double a) { return a * dcmp(a); }//取绝对值
struct Point {
double x, y; Point(double X = 0, double Y = 0) { x = X, y = Y; }
inline void in() { scanf("%lf%lf", &x, &y); }
inline void out() { printf("%.2lf %.2lf\n", x, y); }
};
/*二:【向量】*/
inline double Dot(Point a, Point b) { return a.x * b.x + a.y * b.y; }//【点积】
inline double Cro(Point a, Point b) { return a.x * b.y - a.y * b.x; }//【叉积】
inline double Len(Point a) { return sqrt(Dot(a, a)); }//【模长】
inline double Angle(Point a, Point b) { return acos(Dot(a, b) / Len(a) / Len(b)); }//【两向量夹角】
inline Point Normal(Point a) { return Point(-a.y, a.x); }//【法向量】
inline Point operator+(Point a, Point b) { return Point(a.x + b.x, a.y + b.y); }
inline Point operator-(Point a, Point b) { return Point(a.x - b.x, a.y - b.y); }
inline Point operator*(Point a, double b) { return Point(a.x * b, a.y * b); }
inline bool operator==(Point a, Point b) { return !dcmp(a.x - b.x) && !dcmp(a.y - b.y); }
//两点坐标重合则相等
/*三:【点、向量的位置变换】*/
/*1.【点、向量的旋转】*/
inline Point turn_P(Point a, double theta) {
//【点A\向量A顺时针旋转theta(弧度)】
double x = a.x * cos(theta) + a.y * sin(theta);
double y = -a.x * sin(theta) + a.y * cos(theta);
return Point(x, y);
}
inline Point turn_PP(Point a, Point b, double theta) {
//【将点A绕点B顺时针旋转theta(弧度)】
double x = (a.x - b.x) * cos(theta) + (a.y - b.y) * sin(theta) + b.x;
double y = -(a.x - b.x) * sin(theta) + (a.y - b.y) * cos(theta) + b.y;
return Point(x, y);
}
/*四:【图形与图形之间的关系】*/
/*1.【点与线段】*/
inline int pan_PL(Point p, Point a, Point b) {//【判断点P是否在线段AB上】
return !dcmp(Cro(p - a, b - a)) && dcmp(Dot(p - a, p - b)) <= 0;//做法一
// return !dcmp(Cro(p-a,b-a))&&dcmp(min(a.x,b.x)-p.x)<=0&&
// dcmp(p.x-max(a.x,b.x))<=0&&dcmp(min(a.y,b.y)-p.y)<=0&&
// dcmp(p.y-max(a.y,b.y))<=0;//做法二
//PA,AB共线且P在AB之间(其实也可以用len(p-a)+len(p-b)==len(a-b)判断,但是精度损失较大)
}
inline double dis_PL(Point p, Point a, Point b) {//【点P到线段AB距离】
if (a == b)return Len(p - a);//AB重合
Point x = p - a, y = p - b, z = b - a;
if (dcmp(Dot(x, z)) < 0)return Len(x);//P距离A更近
if (dcmp(Dot(y, z)) > 0)return Len(y);//P距离B更近
return Abs(Cro(x, z) / Len(z));//面积除以底边长
}
/*2.【点与直线】*/
inline int pan_PL_(Point p, Point a, Point b) {
//【判断点P是否在直线AB上】
return !dcmp(Cro(p - a, b - a));//PA,AB共线
}
inline Point FootPoint(Point p, Point a, Point b) {
/
/【点P到直线AB的垂足】
Point x = p - a, y = p - b, z = b - a;
double len1 = Dot(x, z) / Len(z), len2 = -1.0 * Dot(y, z) / Len(z);
//分别计算AP,BP在AB,BA上的投影
return a + z * (len1 / (len1 + len2));//点A加上向量AF
}
inline Point Symmetry_PL(Point p, Point a, Point b) {
//【点P关于直线AB的对称点】
return p + (FootPoint(p, a, b) - p) * 2;//将PF延长一倍即可
}
/*3.【线与线】*/
inline Point cross_LL(Point a, Point b, Point c, Point d) {
//【两直线AB,CD的交点】
Point x = b - a, y = d - c, z = a - c;
return a + x * (Cro(y, z) / Cro(x, y));//点A加上向量AF
}
inline int pan_cross_L_L(Point a, Point b, Point c, Point d) {
//【判断直线AB与线段CD是否相交】
return pan_PL(cross_LL(a, b, c, d), c, d);
//直线AB与直线CD的交点在线段CD上
}
inline int pan_cross_LL(Point a, Point b, Point c, Point d) {
//【判断两线段AB,CD是否相交】
double c1 = Cro(b - a, c - a), c2 = Cro(b - a, d - a);
double d1 = Cro(d - c, a - c), d2 = Cro(d - c, b - c);
return dcmp(c1) * dcmp(c2) < 0 && dcmp(d1) * dcmp(d2) < 0;//分别在两侧
}
/*4.【点与多边形】*/
inline int PIP(Point* P, int n, Point a) {
//【射线法】判断点A是否在任意多边形Poly以内
int cnt = 0; double tmp;
for (int i = 1; i <= n; ++i) {
int j = i < n ? i + 1 : 1;
if (pan_PL(a, P[i], P[j]))return 2;//点在多边形上
if (a.y >= min(P[i].y, P[j].y) && a.y < max(P[i].y, P[j].y))
//纵坐标在该线段两端点之间
tmp = P[i].x + (a.y - P[i].y) / (P[j].y - P[i].y)
* (P[j].x - P[i].x), cnt += dcmp(tmp - a.x) > 0;//交点在A右方
}
return cnt & 1;//穿过奇数次则在多边形以内
}
inline int judge(Point a, Point L, Point R) {//判断AL是否在AR右边
return dcmp(Cro(L - a, R - a)) > 0;//必须严格以内
}
inline int PIP_(Point* P, int n, Point a) {
//【二分法】判断点A是否在凸多边形Poly以内
//点按逆时针给出
if (judge(P[1], a, P[2]) || judge(P[1], P[n], a))return 0;
//在P[1_2]或P[1_n]外
if (pan_PL(a, P[1], P[2]) || pan_PL(a, P[1], P[n]))return 2;
//在P[1_2]或P[1_n]上
int l = 2, r = n - 1;
while (l < r) {
//二分找到一个位置pos使得P[1]_A在P[1_pos],P[1_(pos+1)]之间
int mid = l + r + 1 >> 1;
if (judge(P[1], P[mid], a))l = mid;
else r = mid - 1;
}
if (judge(P[l], a, P[l + 1]))return 0;//在P[pos_(pos+1)]外
if (pan_PL(a, P[l], P[l + 1]))return 2;//在P[pos_(pos+1)]上
return 1;
}
/*5.【线与多边形】*/
/*6.【多边形与多边形】*/
inline int judge_PP(Point* A, int n, Point* B, int m) {
//【判断多边形A与多边形B是否相离】
for (int i1 = 1; i1 <= n; ++i1) {
int j1 = i1 < n ? i1 + 1 : 1;
for (int i2 = 1; i2 <= m; ++i2) {
int j2 = i2 < m ? i2 + 1 : 1;
if (pan_cross_LL(A[i1], A[j1], B[i2], B[j2]))return 0;
//两线段相交
if (PIP(B, m, A[i1]) || PIP(A, n, B[i2]))return 0;//点包含在内
}
}
return 1;
}
/*五:【图形面积】*/
/*1.【任意多边形面积】*/
inline double PolyArea(Point* P, int n) {//任意多边形P的面积,逆时针给出点序,或者先求一遍凸包,把点排序
double S = 0;
for (int i = 1; i <= n; ++i)S += Cro(P[i], P[i < n ? i + 1 : 1]);
return S / 2.0;
}
/*2.【圆的面积并】*/
/*3.【三角形面积并】*/
/*六:【凸包】*/
/*1.【求凸包】*/
inline bool cmp1(Point a, Point b) { return a.x == b.x ? a.y < b.y : a.x < b.x; };
//按坐标排序
inline int ConvexHull(Point* P, int n, Point* cp) {
//【Graham扫描法】求凸包
sort(P + 1, P + n + 1, cmp1);
int t = 0;
for (int i = 1; i <= n; ++i) {//下凸包
while (t > 1 && dcmp(Cro(cp[t] - cp[t - 1], P[i] - cp[t - 1])) <= 0)--t;
cp[++t] = P[i];
}
int St = t;
for (int i = n - 1; i >= 1; --i) {//上凸包
while (t > St && dcmp(Cro(cp[t] - cp[t - 1], P[i] - cp[t - 1])) <= 0)--t;
cp[++t] = P[i];
}
return --t;//要减一
}
/*2.【旋转卡壳】*/
/*3.【半平面交】*/
struct Line {
Point a, b; double k; Line(Point A = Point(0, 0), Point B = Point(0, 0))
{
a = A, b = B, k = atan2(b.y - a.y, b.x - a.x);
}
inline bool operator<(const Line& O)const
{
return dcmp(k - O.k) ? dcmp(k - O.k) < 0 : judge(O.a, O.b, a);
}
//如果角度相等则取左边的
}L[N], Q[N];
inline Point cross(Line L1, Line L2) { return cross_LL(L1.a, L1.b, L2.a, L2.b); }
//获取直线L1,L2的交点
inline int judge(Line L, Point a) { return dcmp(Cro(a - L.a, L.b - L.a)) > 0; }
//判断点a是否在直线L的右边
inline int halfcut(Line* L, int n, Point* P) {//【半平面交】,逆时针,求凸包排点序,再求线Line(p[i - 1], p[i])
sort(L + 1, L + n + 1); int m = n; n = 0;
for (int i = 1; i <= m; ++i)if (i == 1 || dcmp(L[i].k - L[i - 1].k))L[++n] = L[i];
int h = 1, t = 0;
for (int i = 1; i <= n; ++i) {
while (h < t && judge(L[i], cross(Q[t], Q[t - 1])))--t; //当队尾两个直线交点不是在直线L[i]上或者左边时就出队
while (h < t && judge(L[i], cross(Q[h], Q[h + 1])))++h; //当队头两个直线交点不是在直线L[i]上或者左边时就出队
Q[++t] = L[i];
}
while (h < t && judge(Q[h], cross(Q[t], Q[t - 1])))--t;
while (h < t && judge(Q[t], cross(Q[h], Q[h + 1])))++h;
n = 0;
for (int i = h; i <= t; ++i)P[++n] = cross(Q[i], Q[i < t ? i + 1 : h]);
return n;
}
/*4.【闵可夫斯基和】*/
Point V1[N], V2[N];
inline int Mincowski(Point* P1, int n, Point* P2, int m, Point* V) {
//【闵可夫斯基和】求两个凸包{P1},{P2}的向量集合{V}={P1+P2}构成的凸包
for (int i = 1; i <= n; ++i)V1[i] = P1[i < n ? i + 1 : 1] - P1[i];
for (int i = 1; i <= m; ++i)V2[i] = P2[i < m ? i + 1 : 1] - P2[i];
int t = 0, i = 1, j = 1; V[++t] = P1[1] + P2[1];
while (i <= n && j <= m)++t, V[t] = V[t - 1] + (dcmp(Cro(V1[i], V2[j])) > 0 ? V1[i++] : V2[j++]);
while (i <= n)++t, V[t] = V[t - 1] + V1[i++];
while (j <= m)++t, V[t] = V[t - 1] + V2[j++];
return t;
}
/*5.【动态凸包】*/
/*七:【圆】*/
/*1.【三点确定一圆】*/
#define S(a) ((a)*(a))
struct Circle { Point O; double r; Circle(Point P, double R = 0) { O = P, r = R; } };
inline Circle getCircle(Point A, Point B, Point C) {
//【三点确定一圆】暴力解方程
double x1 = A.x, y1 = A.y, x2 = B.x, y2 = B.y, x3 = C.x, y3 = C.y;
double D = ((S(x2) + S(y2) - S(x3) - S(y3)) * (y1 - y2) - (S(x1) + S(y1) -
S(x2) - S(y2)) * (y2 - y3)) / ((x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2));
double E = (S(x1) + S(y1) - S(x2) - S(y2) + D * (x1 - x2)) / (y2 - y1);
double F = -(S(x1) + S(y1) + D * x1 + E * y1);
return Circle(Point(-D / 2.0, -E / 2.0), sqrt((S(D) + S(E) - 4.0 * F) / 4.0));
}
inline Circle getcircle(Point A, Point B, Point C) {
//【三点确定一圆】向量垂心法
Point P1 = (A + B) * 0.5, P2 = (A + C) * 0.5;
Point O = cross_LL(P1, P1 + Normal(B - A), P2, P2 + Normal(C - A));
return Circle(O, Len(A - O));
}
/*2.【最小覆盖圆】*/
inline int PIC(Circle C, Point a) { return dcmp(Len(a - C.O) - C.r) <= 0; }
//判断点A是否在圆C内
inline void Random(Point* P, int n)
{
for (int i = 1; i <= n; ++i)swap(P[i], P[rand() % n + 1]);
}//随机一个排列
inline Circle Min_Circle(Point* P, int n) {//【求点集P的最小覆盖圆】
// random_shuffle(P+1,P+n+1);
Random(P, n); Circle C = Circle(P[1], 0);
for (int i = 2; i <= n; ++i)if (!PIC(C, P[i])) {
C = Circle(P[i], 0);
for (int j = 1; j < i; ++j)if (!PIC(C, P[j])) {
C.O = (P[i] + P[j]) * 0.5, C.r = Len(P[j] - C.O);
for (int k = 1; k < j; ++k)if (!PIC(C, P[k]))C = getcircle(P[i], P[j], P[k]);
}
}
return C;
}
/*3.【三角剖分】*/
inline double calc(Point A, Point B, Point O, double R) {//【三角剖分】
if (A == O || B == O)return 0;
int op = dcmp(Cro(A - O, B - O)) > 0 ? 1 : -1; double ans = 0;
Point x = A - O, y = B - O;
int flag1 = dcmp(Len(x) - R) > 0, flag2 = dcmp(Len(y) - R) > 0;
if (!flag1 && !flag2)ans = Abs(Cro(A - O, B - O)) / 2.0;//两个点都在里面
else if (flag1 && flag2) {//两个点都在外面
if (dcmp(dis_PL(O, A, B) - R) >= 0)ans = R * R * Angle(x, y) / 2.0;//完全包含了圆弧
else {//分三段处理 △+圆弧+△
if (dcmp(Cro(A - O, B - O)) > 0)swap(A, B);//把A换到左边
Point F = FootPoint(O, A, B); double lenx = Len(F - O), len = sqrt(R * R - lenx * lenx);
Point z = turn_P(F - O, Pi / 2.0) * (len / lenx); Point B_ = F + z, A_ = F - z;
ans = R * R * (Angle(A - O, A_ - O) + Angle(B - O, B_ - O)) / 2.0 + Cro(B_ - O, A_ - O) / 2.0;
}
}
else {//一个点在里面,一个点在外面
if (flag1)swap(A, B);//使A为里面的点,B为外面的点
Point F = FootPoint(O, A, B); double lenx = Len(F - O), len = sqrt(R * R - lenx * lenx);
Point z = turn_P(F - O, Pi / 2.0) * (len / lenx); Point C = dcmp(Cro(A - O, B - O)) > 0 ? F - z : F + z;
ans = Abs(Cro(A - O, C - O)) / 2.0 + R * R * Angle(C - O, B - O) / 2.0;
}
return ans * op;
}
计算机和三维球的交
\(S=2\pi∗R∗H\)
\(V=\pi*H^2*\frac{3*R-H}{3}\)
#define sqr(n) ((n) * (n))
const double PI = acos(-1.0);
struct point { double x, y, z; };
struct circle { point o; double r; };
double getlen(point a, point b) {
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z));
}
double getCroArea(circle a, circle b) {
if (a.r > b.r) swap(a, b);
double dis = getlen(a.o, b.o);
if (dis + a.r <= b.r) {
double r = max(a.r, b.r);
return 4 * PI * r * r;
} else if (dis < a.r + b.r && dis + a.r > b.r) {
double angle_cosa = (a.r * a.r + dis * dis - b.r * b.r) / (2 * a.r * dis);
double angle_cosb = (b.r * b.r + dis * dis - a.r * a.r) / (2 * b.r * dis);
double len_a = a.r - a.r * angle_cosa, len_b = b.r - b.r * angle_cosb;
double ans = 4 * PI * (a.r * a.r + b.r * b.r);
ans -= 2 * PI * (a.r * len_a + b.r * len_b);
return ans;
}
else return 4 * PI * (a.r * a.r + b.r * b.r);
return 0;
}
double getCroVol(circle o, circle t) {
if (o.r < t.r) swap(o, t);
double dis = sqrt(sqr(o.o.x - t.o.x) + sqr(o.o.y - t.o.y) + sqr(o.o.z - t.o.z)), ans = 0;
if (dis <= o.r - t.r)
ans = 4.0 / 3 * PI * t.r * t.r * t.r;
else if (dis < o.r + t.r) {
double cal = (o.r * o.r + dis * dis -t.r * t.r) / (dis * o.r * 2);
double h = o.r * (1 - cal);
ans = PI / 3 * (3 * o.r - h) * h * h;
cal = (t.r * t.r + dis * dis - o.r * o.r) / (dis * t.r * 2);
h = t.r * (1 - cal);
ans += PI / 3 * (3 * t.r - h) * h * h;
}
return ans;
}
乱七八糟的公式
莫比乌斯
b(abc) 为 abc的因子个数
\(f(a,b,c) = \sum_i^a\sum_j^b\sum_k^cb(a*b*c) ≡ g(a,b,c) = \sum_{gcd(i,j,k)=1} \left \lfloor \frac{a}{i} \right \rfloor \left \lfloor \frac{b}{j} \right \rfloor \left \lfloor \frac{c}{k} \right \rfloor\)
\(f(a,b)=\sum_i^a\sum_j^bb(a*b) ≡ g(a,b) = \sum_{gcd(i,j)=1}\left \lfloor \frac{a}{i} \right \rfloor \left \lfloor \frac{b}{j} \right \rfloor\)
图论
最短路
迪杰斯特拉
无法判负边,负环
贝尔曼
可以判负环, 用spfa, 最坏 O(NM), 可以判负环进行差分约束
bfs判负环
bool check(double mid) {
stack<int> st; rep(i, 1, n) dis[i] = dep[i] = 0, v[i] = 1, st.push(i);
while (!st.empty()) {
int x = st.top(); st.pop(); v[x] = 0;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
if (dis[y] <= dis[x] + co[i]) continue;
dis[y] = dis[x] + co[i]; dep[y] = dep[x] + 1;
if (dep[y] >= n) return 1; /*有负环*/ if (!v[y]) st.push(y), v[y] = 1;
}
} return 0;
}
dfs 判负环
bool dfs(int x) {
v[x] = 1;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
if (dis[y] > dis[x] + co[i]) {
dis[y] = dis[x] + co[i];
if (v[y] || dfs(y)) return 1; //有负环
}
return v[x] = 0;
}
弗洛伊德
rep (k, 1, n) rep (i, 1, n) rep (j, 1, n) umin(d[i][j], d[i][k] + d[k][j]);
最小生成树
kruskal
prim
次小严格生成树
找到非生成边(x, y)去替换树上x到y路径的最大边,费用差值最小,就是次小和最小生成树的差值
const int N = 1e5 + 5, M = 3e5 + 5;
struct STFrom {
int f[N][20], dep[N], lg[N], t;//N为节点的数量
ll d[N][20], b[N][20];
vector<PII> *h;
void init(int n, vector<PII> *H) {
t = log2(n - 1) + 1; h = H; lg[0] = -1;
rep(i, 1, n) dep[i] = 0, lg[i] = lg[i >> 1] + 1;
}
void bfs(int s) {
queue<int> q; q.push(s); dep[s] = 1;
rep(i, 0, t) f[s][i] = 0, d[s][i] = b[s][i] = 0;
while (!q.empty()) {
int x = q.front(); q.pop();
for (auto &y : h[x]) {
if (dep[y.fi]) continue;
dep[y.fi] = dep[x] + 1; f[y.fi][0] = x; q.push(y.fi);
d[y.fi][0] = y.se; b[y.fi][0] = -2e18;
for (int j = 1; j <= t; ++j) {
f[y.fi][j] = f[f[y.fi][j - 1]][j - 1];
if (d[f[y.fi][j - 1]][j - 1] > d[y.fi][j - 1])
b[y.fi][j] = max(d[y.fi][j - 1], b[f[y.fi][j - 1]][j - 1]),
d[y.fi][j] = d[f[y.fi][j - 1]][j - 1];
else if (d[f[y.fi][j - 1]][j - 1] == d[y.fi][j - 1])
b[y.fi][j] = max(b[y.fi][j - 1], b[f[y.fi][j - 1]][j - 1]),
d[y.fi][j] = d[y.fi][j - 1];
else b[y.fi][j] = max(b[y.fi][j - 1], d[f[y.fi][j - 1]][j - 1]),
d[y.fi][j] = d[y.fi][j - 1];
}
}
}
}
void work(PLL& ans, int y, int i) {
if (d[y][i] > ans.fi) ans.se = max(ans.fi, b[y][i]), ans.fi = d[y][i];
else if (d[y][i] == ans.fi) umax(ans.se, b[y][i]);
else umax(ans.se, d[y][i]);
}
PLL ask(int x, int y) {
PLL ans = {-2e18, -2e18};
if (dep[x] > dep[y]) swap(x, y);
for(int k = dep[y] - dep[x], i = lg[k]; ~i; --i) if (k >> i & 1)
work(ans, y, i), y = f[y][i];k
if (x == y) return ans;
per(i, lg[dep[y]], 0) if (f[x][i] ^ f[y][i]) {
work(ans, x, i); work(ans, y, i);
x = f[x][i], y = f[y][i];
}
work(ans, x, 0); work(ans, y, 0);
return ans;
}
} ST;
struct edge {int x, y; ll c; bool f = 0; } e[M];
int n, m, _, k, cas, f[N];
ll res, ans = 2e18;
vector<PII> h[N];
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
int main() {
IOS; cin >> n >> m;
rep (i, 1, m) cin >> e[i].x >> e[i].y >> e[i].c;
sort(e + 1 , e + 1 + m, [](edge& a, edge& b) { return a.c < b.c; });
rep (i, 1, n) f[i] = i;
rep (i, 1, m) {
int x = find(e[i].x), y = find(e[i].y);
if (x == y) continue;
res += e[i].c, e[i].f = 1; f[y] = x;
h[e[i].x].pb(e[i].y, e[i].c); h[e[i].y].pb(e[i].x, e[i].c);
}
ST.init(n, h); ST.bfs(1);
rep (i, 1, m) if (!e[i].f) {
auto cur = ST.ask(e[i].x, e[i].y);
if (cur.fi < e[i].c) umin(ans, res - cur.fi + e[i].c);
else if(cur.se != -2e18 && cur.se < e[i].c) umin(ans, res - cur.se + e[i].c);
}
return cout << ans, 0;
}
最小树形图朱刘算法O(NM)
struct Edge { int x, y, w; } e[N];
ll zhuLiu (int root, int n, vector<Edge>& e) { //从0开始,下标从1开始传n + 1
ll ans = 0; VI in(n), pre(n); //in为最小入边权, pre为其对应的起点
while (true) {
for (auto &i : in) i = INF;
//可以添加虚点,连向每个点w=inf,ans>=2*inf无解,在添加最小边的时
//选了虚边,另一端是实际源点,但点的编号已经改变,只能记录用了哪条边,最后还原点
/* if (e[i].w < in[e[i].y] && e[i].x != e[i].y) {
pre[e[i].y] = e[i].x, in[e[i].y] = e[i].w;*/
/*if (e[i].x == root) pos = i;//记录连接实际root的虚边
} */
for (auto &i:e) if (i.w < in[i.y] && i.x != i.y) pre[i.y] = i.x, in[i.y] = i.w;
rep (i, 0, n - 1) if (i != root && in[i] == INF) return -1;
int cnt = in[root] = 0; VI idx(n, -1), v(n, -1);
rep (i, 0, n - 1) {//标记每个环
int y = i; ans += in[i];//记录权值
while (v[y] != i && idx[y] == -1 && y != root) v[y] = i, y = pre[y];
if (y != root && idx[y] == -1) {
for(int x = pre[y]; x != y; x = pre[x]) idx[x] = cnt;
idx[y] = cnt++;
}
} if (cnt == 0) break;
for (auto &i : idx) if (i == -1) i = cnt++;
for (auto &i : e) {
if (idx[i.x] != idx[i.y]) i.w -= in[i.y];
i.x = idx[i.x], i.y = idx[i.y];
} n = cnt; root = idx[root];
} return ans;
}
树的重心 (最多两个且相连)
int mx; VI gra;
void dfs(int x, int fa) {
siz[x] = 1; int max_size = 0;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
if (y == fa) continue;
dfs(y); siz[x] += siz[y]; umax(max_size, siz[y]);
}
umax(max_size, n - siz[x]);
if (mx > max_size) mx = max_size, gra.resize(0), gra.pb(x);
else if (mx == max_size) gra.pb(x);
}
差分约束
记得在加完题目约束之后,别忘了i和i+1的默认约束,且别忘了超级源点让图联通
树的直径(可以有很多条)
两次dfs求出最远的两个点 (保留路径方便, 边权为非负)
void dfs(int u, int fa) {
for (int i = h[u], y = to[i]; i; y = to[i = ne[i]]) {
if (fa != y) continue;
f[y] = u; b[y] = i; d[y] = d[u] + co[i];
if (d[0] < d[y]) d[0] = d[y], f[0] = y;
dfs(y, x);
}
}
void work(int& p, int& q) {
d[0] = -N; d[1] = 0; dfs(1, 0); p = f[0];
d[0] = -N; d[p] = 0; dfs(p, 0); q = f[0];
}
dp(边权任意, 保留路径困难)
void dpfind(int u, int fa, int& ans) {
for (int i = h[u]; i; i = ne[i]) {
int y = to[i];
if (fa != y) continue;
dpfind(y, u, ans);
umax(ans, d[u] + d[y] + co[i]), umax(d[u], d[y] + co[i]);
} umax(d[u], 0);
}
最近公共祖先
struct STFrom {
int f[N][20], dep[N], lg[N], t;//N为节点的数量
int *h, *ne, *to;
void init(int n, int* H, int* Ne, int* To) {
t = log2(n - 1) + 1; h = H, ne = Ne, to = To; lg[0] = -1;
rep(i, 1, n) dep[i] = 0, lg[i] = lg[i >> 1] + 1;
}
void bfs(int s) {
queue<int> q; q.push(s); dep[s] = 1;
rep(i, 0, t) f[s][i] = 0;
while (!q.empty()) {
int x = q.front(); q.pop();
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
if (dep[y]) continue;
dep[y] = dep[x] + 1; f[y][0] = x; q.push(y);
for (int j = 1; j <= t; ++j) f[y][j] = f[f[y][j - 1]][j - 1];
}
}
}
int lca(int x, int y) {
if (dep[x] > dep[y]) swap(x, y);
for (int k = dep[y] - dep[x]; ~lg[k]; k ^= 1 << lg[k]) y = f[y][lg[k]];
if (x == y) return x;
per(i, lg[dep[y]], 0) if (f[x][i] ^ f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
int dist(int x, int y) { return dep[x] + dep[y] - (dep[lca(x, y)]<<1); }
} ST;
基环树
所有问题都分为, 在同一颗子树和环上不同子树来处理,
且处理子树, 莫走到其他子树上, 直接从环上节点作为子树根, 然后再也不走到环上
无向基环树
pair<int, ll> cir[N]; //<环上节点编号, 从第个点到此点的距离>
bool v[N]; //是否在环上
int dfsc(int u, int bian) {
idx[u] = idcnt; int s = 0;
for (int i = h[u]; i; i = ne[i]) {
int y = to[i];
if (bian == (i ^ 1) || v[y]) continue;
if (!idx[y]) s = max(s, dfsc(y, i));
else cir[idc[s = u] = ++cid] = u, v[u] = 1;
}
return s;
}
void work(int u) {
cid = 0; ++idcnt;
for (u = a[dfsc(u, 0)], ls = 0;u != cir[1]; ls = idc[u], u = a[u])
cir[idc[u] = ++cid] = { u, cir[ls].se + 1 }, v[u] = 1;
sizc[idcnt] = cid;
}
无向基环树森林直径和
rep (i, 1, n) if (!dfn[i]) ans += work(i)
ll work(int u) {
ll ans = cid = 0; tarjan(u, 0);//找出当前基环树的环
rep (i, 1, cid) {
cir[i + cid] = cir[i]; cir[i + cid].se += cir[cid].se + cir[0].se;
dpfind(cir[i].fi, 0, ans); //dp找环上节点子树的直径
}
int tail = -1, head = 0;; cid <<= 1;
q[++tail] = { 1, dis[cir[1].fi] };
rep(i, 2, cid) {
while (i - q[head].fi >= cid >> 1) ++head;
ans = max(ans, dis[cir[i].fi] + cir[i].se + q[head].se);
ll w = dis[cir[i].fi] - cir[i].se;
while (head <= tail && q[tail].se <= w) --tail;
q[++tail] = { i, w };
} return ans;
}
内向树森林
要反向建边, 才能从环上子树走向环
树链剖分
基础
struct BIT {
static const int N = 1e5 + 5;
struct node { int l, r, len; ll val, tag; } tr[N << 2];
void push_up(int rt) { tr[rt].val = (tr[rt << 1 | 1].val + tr[rt << 1].val) % mod; }
void push_down(int rt) {
if (!tr[rt].tag) return;
tr[rt << 1].val = (tr[rt << 1].val + tr[rt << 1].len * tr[rt].tag % mod) % mod;
tr[rt<<1|1].val=(tr[rt<<1|1].val+tr[rt<<1|1].len*tr[rt].tag%mod)%mod;
tr[rt << 1].tag = (tr[rt << 1].tag + tr[rt].tag) % mod;
tr[rt << 1 | 1].tag = (tr[rt << 1 | 1].tag + tr[rt].tag) % mod; tr[rt].tag = 0;
}
void build(int rt, int l, int r, int* a) {
tr[rt] = { l, r, r - l + 1, 0 };
if (l == r) { tr[rt].val = a[l] % mod; return; }
int mid = l + r >> 1;
build(rt << 1, l, mid, a); build(rt << 1 | 1, mid + 1, r, a); push_up(rt);
}
void change(int rt, int l, int r, ll k) {
if (tr[rt].l >= l && tr[rt].r <= r) {
tr[rt].val = (tr[rt].val + tr[rt].len * k % mod) % mod;
tr[rt].tag = (k + tr[rt].tag) % mod; return;
}
push_down(rt);
int mid = tr[rt].l + tr[rt].r >> 1;
if (mid >= l) change(rt << 1, l, r, k);
if (mid < r) change(rt << 1 | 1, l, r, k);
push_up(rt);
}
ll ask(int rt, int l, int r) {
if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt].val;
push_down(rt);
int mid = tr[rt].l + tr[rt].r >> 1;
ll ans = mid >= l ? ask(rt << 1, l, r) : 0;
if (mid < r) ans = (ans + ask(rt << 1 | 1, l, r)) % mod;
push_up(rt); return ans;
}
} bit;
VI h[N];
int a[N], na[N], dep[N], hson[N], siz[N], fa[N], top[N], dfn[N], df;
void findhson(int x, int f) {
dep[x] = dep[f] + 1; siz[x] = 1; fa[x] = f;
int mx = 0;
for (auto y : h[x]) {
if (y == f) continue; findhson(y, x);
if (mx < siz[y]) mx = siz[y], hson[x] = y; siz[x] += siz[y];
}
}
void dfs(int x, int f) {
na[dfn[x] = ++df] = a[x]; top[x] = f;
if (!hson[x]) return;
dfs(hson[x], f);
for (auto y : h[x]) if (y != fa[x] && y != hson[x]) dfs(y, y);
}
void solve1() { //将树从 x 到 y 结点最短路径上所有节点的值都加上 z
int x, y, z; cin >> x >> y >> z; z %= mod;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
bit.change(1, dfn[top[x]], dfn[x], z); x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
bit.change(1, dfn[x], dfn[y], z);
}
ll solve2() { //求树从 x 到 y 结点最短路径上所有节点的值之和
int x, y; cin >> x >> y; ll ans = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
ans = (ans + bit.ask(1, dfn[top[x]], dfn[x])) % mod; x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
return ((ans + bit.ask(1, dfn[x], dfn[y])) % mod + mod) % mod;
}
void solve3() { //将以 x 为根节点的子树内所有节点值都加上 z
int x, z; cin >> x >> z; z %= mod;
bit.change(1, dfn[x], dfn[x] + siz[x] - 1, z);
}
ll solve4() { //求以 x 为根节点的子树内所有节点值之和
int x; cin >> x;
return (bit.ask(1, dfn[x], dfn[x] + siz[x] - 1) + mod) % mod;
}
int main() {
IOS; cin >> n >> m >> s >> mod;
rep(i, 1, n) cin >> a[i];
rep(i, 2, n) { int u, v; cin >> u >> v; h[u].pb(v); h[v].pb(u); }
findhson(s, 0); dfs(s, s); bit.build(1, 1, df, na);
rep(i, 1, m) {
int op; cin >> op;
switch (op) {
case 1: solve1(); break;
case 2: cout << solve2() << '\n'; break;
case 3: solve3(); break;
case 4: cout << solve4() << '\n'; break;
}
} return 0;
}
点分树
struct STFrom { ... } ST; //LCA的ST表
struct DTTree {
static const int N = 1e5 + 5;
struct node { int fa; } t[N];
int mxsz[N], csz[N], rt, siz[N];
int* h, * ne, * to;
bool v[N];
void init(int n, int* H, int* Ne, int* To) {
h = H, ne = Ne, to = To; rt = 0;
rep(i, 1, n) v[i] = t[i].fa = 0;
}
void dfscenter(int x, int f, int sum) {
csz[x] = 1; mxsz[x] = 0;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) if (y != f && !v[y]) {
dfscenter(y, x, sum); csz[x] += csz[y]; mxsz[x] = max(mxsz[x], csz[y]);
} umax(mxsz[x], sum - csz[x]);
if (!rt || mxsz[x] < mxsz[rt]) rt = x;
}
void dfs(int x, int sum) {
v[x] = 1; siz[x] = sum; rt = 0;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]], rt = 0) if (!v[y]) {
int yz = csz[y] < csz[x] ? csz[y] : sum - csz[x];
dfscenter(y, x, yz); t[rt].fa = x; dfs(rt, yz);
}
}
} DT;
struct DyBIT {
static const int n = 2e5 + 5;
struct Node { int l, r, val, lson, rson;} tr[N * 20];
int root[N], tot;
void init(int n) { rep(i, 1, n) root[i] = 0; tot = 0; }
int newNode(){++tot; tr[tot].lson = tr[tot].rson = tr[tot].val = 0; return tot;}
void change(int& rt, int l, int r, int p, int k) {
if (!rt) rt = newNode(), tr[rt].l = l, tr[rt].r = r;
tr[rt].val += k; if (l == r) return;
int mid = l + r >> 1;
if (p <= mid) change(tr[rt].lson, l, mid, p, k);
else change(tr[rt].rson, mid + 1, r, p, k);
}
int ask(int rt, int l, int r, int L, int R) {
if (!rt || L > R) return 0;
if (l >= L && r <= R) return tr[rt].val;
int mid = l + r >> 1;
int ans = L <= mid ? ask(tr[rt].lson, l, mid, L, R) : 0;
if (R > mid) ans += ask(tr[rt].rson, mid + 1, r, L, R);
return ans;
}
} bita, bitb;
int w[N];
void change(int x, int k) {
for (int p = x, fa; p; p = fa) {
fa = DT.t[p].fa; bita.change(bita.root[p], 0, DT.siz[p], ST.dist(p, x), k);
if (fa) bitb.change(bitb.root[p], 0, DT.siz[fa], ST.dist(fa, x), k);
}
}
int ask(int x, int k) {
int ans = 0;
for(int u=x,p=0,d=k-ST.dist(u,x);u;p=u,d=k-ST.dist(u=DT.t[u].fa,x))if(d >= 0){
ans += bita.ask(bita.root[u], 0, DT.siz[u], 0, d);
if (p) ans -= bitb.ask(bitb.root[p], 0, DT.siz[u], 0, d);
} return ans;
}
int main() {
IOS; while (cin >> n >> m) {
rep(i, 1, n) cin >> w[i], h[i] = 0; tot = 0;
rep(i, 2, n) {int u, v; cin >> u >> v; add(u, v); add(v, u);}
bita.init(n); bitb.init(n);
ST.init(n, h, ne, to); ST.bfs(1);
DT.init(n, h, ne, to); DT.dfscenter(1, 0, n); DT.dfs(DT.rt, n);
rep(i, 1, n) change(i, w[i]); //int las = 0;
rep(i, 1, m) {
string op; int x, y; cin >> op >> x >> y;//x ^= las, y ^= las;
if (op[0] == '?') cout << ask(x, y) << '\n';
else change(x, y - w[x]), w[x] = y;
}
} return 0;
}
无向图连通性
e-dcc缩点, 求桥
int c[N], ecnt;
vector<vector<int>> ecc;
bool edge[M << 1];
void tarjan(int x, int bian) {
dfn[st[++top] = x] = low[x] = ++df;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
if (!dfn[y]) {
tarjan(y, i ^ 1); umin(low[x], low[y]);
if (dfn[x] < low[y]) edge[i] = edge[i ^ 1] = 1;
}
else if (i ^ bian) low[x] = min(low[x], dfn[y]);
if (low[x] == dfn[x]) {
++ecnt; ecc.pb(VI()); int y;
do { c[y = st[top--]] = ecnt; ecc.back().pb(y); } while (y != x);
}
}
rep (i, 1, n) if (!dfn[i]) top = 0, tarjan(i, 0);//遍历
rep (i, 2, tot) { //可能有重边, 新建缩点图
int x = to[i ^ 1], y = to[i];
if (c[x] == c[y]) continue;
add_c(c[x], c[y]);
}
v-dcc, 求割点
int newid[N], pre[N], num, c[N], bl[M]; //bl[i], 原先的边属于哪一个vcc
vector<vector<int>> dcc;
bool cut[N];
void tarjan(int x) {
dfn[x] = low[x] = ++df;
if (!h[x]) { dcc.pb(vector<int>(1, x)); return; }
st[++top] = x; int cnt = 0;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
if (!dfn[y]) {
tarjan(y); low[x] = min(low[x], low[y]);
if (dfn[x] <= low[y]) {
++cnt; int z;
dcc.pb(vector<int>());
if (x != root || cnt > 1) cut[x] = 1;
do dcc.back().pb(z = st[top--]); while (z != y);
dcc.back().pb(x);
}
}
else umin(low[x], dfn[y]);
}
rep (i, 1, n) if (!dfn[i]) top = 0, root = i, tarjan(i);//遍历
num = dcc.size();
rep (i, 1, n) if (cut[i]) newid[i] = ++num, pre[num] = i; //切点新编号
per (i, dcc.size(), 1)
for (int &j : dcc[i - 1]) {
c[j] = i;
if (cut[j]) add_c(newid[j], i), add_c(i, newid[j]);
for (int k = h[j]; k; k = ne[k]) if (c[to[k]] == i) bl[k >> 1] = i;
}
欧拉回路
bool eulerc(int s) {
for (auto& i : edge)
if (i.size() & 1) return 0; //存在入度为奇数, 无欧拉回路
else sort(all(i), greater<PII>()); //把边按照边的权值排序(使得欧拉回路输出字典序最小)
top = t = 0; st[++top] = 0; st[++top] = s;
while (top) {
int x = st[top];
while (!edge[x].empty() && vis[edge[x].back().fi]) edge[x].pop_back();
if (!edge[x].empty()) {
st[++top] = edge[x].back().fi;
st[++top] = edge[x].back().se;
vis[st[top - 1]] = 1;
//标记边(编号)使用过, 另一次访问次边时边的另一节点,
//如果边可以来回走一次, 那就把加入反边编号
edge[x].pop_back();
}
else ans[++t] = st[(--top)--];
}
return 1;
}
仙人掌(连通图,一条边最多在一个环上) 圆方树
圆方树,圆点是原来的点,方点代表一个环(拆环),环上的点向这个环对应的方点连边
void solve(int x, int y, int c) { //再次点双中, 以x为起点, 以x->y方向遍历次点双
//sum[i]表示从x->y方向到i,i距离x的距离,sum[vcnt+n]是这个点双的环的大小
sum[++vcnt + n] = sum[y] = c;
for (int i = y; i != x; i = fa[i]) sum[fa[i]] = sum[vcnt + n] += dist[i];
sum[x] = 0;
for (int i = y; i != fa[x]; i = fa[i]) { //点双上园点向方点连边
int c = min(sum[i], sum[vcnt + n] - sum[i]);
add_c(i, vcnt + n, c); add_c(vcnt + n, i, c);
}
}
void tarjan(int x) {
dfn[x] = low[x] = ++df;
if (!h[x]) return; st[++top] = x;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
if (!dfn[y]) {
dist[y] = co[i]; fa[y] = x;
tarjan(y); umin(low[x], low[y]);
if (dfn[x] <= low[y]) { //找到一个点双
int z = st[top], c;
while (y != st[top--]);
for(int j = h[z]; j; j = ne[j]) if(to[j] == x) { c = co[j]; break; }
solve(x, z, c);
}
}
else umin(low[x], dfn[y]);
}
有向图的连通性
scc缩点
int c[N], scnt;
vector<VI> scc;
bool inst[N]; //是否在栈中
void tarjan(int x) {
dfn[x] = low[x] = ++df; inst[st[++top] = x] = 1;
for (int i = h[x], y = to[i] ; i; y = to[i = ne[i]])
if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
else if (inst[y]) low[x] = min(low[x], dfn[y]);
if (low[x] == dfn[x]) {
++scnt; scc.pb(VI());
for (int y = 0; y ^ x; y = st[top--])
inst[st[top]] = 0, c[st[top]] = scnt, scc.back().pb(st[top]);
}
}
rep (i, 1, n)
for (int k = h[i], y; k; k = ne[k]) {
if (c[i] == c[y = to[k]]) continue;
add_c(c[i], c[y]);
}
2-SAT问题
int n, m, _, k, h[N][N];
int dfn[N], low[N], df, st[N], top;
int c[N], scnt, opp[N], val[N];
bool inst[N];
pair<int, int> t[N];
void tarjan(int x) {
dfn[x] = low[x] = ++df, inst[st[++top] = x] = 1;
for (int y = 1; y <= m; ++y) if (h[x][y])
if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
else if (inst[y]) low[x] = min(low[x], dfn[y]);
if (low[x] == dfn[x]) {
++scnt; int y;
do inst[y = st[top--]] = 0, c[y] = scnt; while (y != x);
}
}
void _print(int c) {
int h = c / 60, m = c % 60;
if (h < 10) printf("0%d:", h);
else printf("%d:", h);
if (m < 10) printf("0%d", m);
else printf("%d", m);
}
void print(int x) { _print(t[x].first); printf(" "); _print(t[x].second); puts(""); }
int main() {
scanf("%d", &n); bool f = 1; m = n << 1;
for (int i = 1; i <= n; ++i) {
int a, b, c, d, e; opp[i] = i + n, opp[i + n] = i;
scanf("%d:%d %d:%d %d", &a, &b, &c, &d, &e);
t[i] = { a * 60 + b, a * 60 + b + e }; t[i + n] = { c * 60 + d - e, c * 60 + d };
if (t[i].first > t[i].second || t[i + n].first > t[i + n].second) f = 0;
}
for (int i = 1; i < m; ++i) for (int j = i + 1; j <= m; ++j)
if (i == j || j == i + n) continue;
else if (t[i].second > t[j].first && t[i].first < t[j].second) h[i][opp[j]] = h[j][opp[i]] = 1;
for (int i = 1; i <= m; ++i) if (!dfn[i]) top = 0, tarjan(i);
for (int i = 1; i <= n; ++i) if (c[i] == c[i + n]) return puts("NO"), 0;
puts("YES");
for (int i = 1; i <= m; ++i) val[i] = c[i] > c[opp[i]];
for (int i = 1; i <= n; ++i) print(val[i] ? opp[i] : i);
return 0;
}
第 i 对情侣需要 Di 分钟完成这个仪式,即必须选择 Si∼Si+Di 或 Ti−Di∼Ti 两个时间段之一。
牧师想知道他能否满足每场婚礼的要求,即给每对情侣安排Si∼Si+Di 或 Ti−Di∼Ti,使得这些仪式的时间段不重叠。
rep (i, 1, n) opp[i] = n + i, opp[n + i] = i;
rep (i, 1, n << 1) if (!dfn[i]) top = 0, tarjan(i);
rep (i, 1, n) if (c[i] == c[i + n]) { puts("-1"); break; }
rep (i, 1, n << 1) val[i] = c[i] > c[opp[i]];
// val[i] == 0, 选择 i, val[i] == 1, 选择 i + n
SCC求割点割边
Lengauer-Tarjan(支配树)
VI ha[N], hb[N], hc[N];//正边,反边,被半支配点
int idx[N], dfn[N], df, fa[N], anc[N], best[N];
int idom[N], sdom[N];
void dfs(int x) {
idx[dfn[x] = ++df] = x;
for (auto &y : ha[x]) if (!dfn[y]) dfs(y), fa[y] = x;
}
int find(int x) {
if (x == anc[x]) return x;
int y = find(anc[x]);
if (dfn[sdom[best[anc[x]]]] < dfn[sdom[best[x]]]) best[x] = best[anc[x]];
return anc[x] = y;
}
int eval(int x) { find(x); return best[x]; }
void tarjan() {
per (y, df, 2) {
int x = idx[y];
for (auto &z : hb[x]) {
if (!dfn[z]) continue; find(z);
if (dfn[sdom[best[z]]] < dfn[sdom[x]]) sdom[x] = sdom[best[z]];
}
hc[sdom[x]].pb(x);
x = anc[x] = fa[x];
for (auto &z : hc[x]) {
int u = eval(z);
idom[z] = sdom[u] == x ? x : u;//最近支配点
}
}
rep (i, 2, df) {
int x = idx[i];
if (idom[x] != sdom[x]) idom[x] = idom[idom[x]];
}
}
rep (i, 1, n) sdom[i] = anc[i] = best[i] = i;
dfs(0); tarjan();
给定s,t的必经边、点
拓扑排序求,fs[x]起点s到x的路径数,ft[x]x到终点t的路径数,双\三hash存储(爆ll)
fs[x]ft[y]=fs[t],(x,y)必经边;fs[x]ft[x]=fs[t],x是必经点
二分图的匹配
最大匹配=最小点覆盖=n-最大独立集大小,最大团等于补图的最大独立集
有向无环图最小路径点覆盖, (路径可覆盖则跑一遍传递闭包), 将每个点拆成入点和出点, n - 拆点二分图最大匹配
二分图的判定
染色法
最大匹配(NN+NM)
rep (i, 1, n) { rep (j, 1, n) v[j] = 0; m += dfs(i); }
bool dfs(int x) {
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
if (v[y]) continue; v[y] = 1;
if (!match[y] || dfs(match[y])) return match[y] = x, 1;
} return 0;
}
KM带权(最大)匹配\((N^3)\)
int w[N][N], la[N], lb[N], match[N], delta; //边权,左、右顶表
bool va[N], vb[N]; //左右点是否在交错树中
bool dfs(int x) {
va[x] = 1;
rep (y, 1, n) if (!vb[y])
if (la[x] + lb[y] == w[x][y]) {
vb[y] = 1;
if (!match[y] || dfs(match[y])) { match[y] = x; return 1; }
}
return 0;
}
int KM() {
rep (i, 1, n) {
la[i] = -inf; lb[i] = 0;
rep (j, 1, n) umax(la[i], w[i][j]);
}
rep (i, 1, n)
while (1) {
rep (i, 1, n) va[i] = vb[i] = 0;/*;*/delta = inf; if (dfs(i)) break;
rep (x, 1, n) if (va[x]) rep (y, 1, n) if (!vb[y]) delta = min(delta, la[x] + lb[y] - w[x][y]);
rep (j, 1, n) la[j] -= va[j] ? delta : 0, lb[j] += vb[j] ? delta : 0;
}
ll ans = 0; rep (i, 1, n) ans += w[match[i]][i]; return ans;
}
一般图(带花树)O(n^2m)
int que[M], ql, qr, pre[N], tim = 0;
int match[N], f[N], tp[N], tic[N];
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
int lca(int x,int y) {
for (++tim; ; swap(x,y)) if (x) {
x = find(x);
if (tic[x] == tim) return x;
else tic[x] = tim, x = pre[match[x]];
}
}
void shrink(int x, int y, int p) {
while (find(x) != p) {
pre[x] = y, y = match[x];
if (tp[y] == 2) tp[y] = 1, que[++qr] = y;
if (find(x) == x) f[x] = p;
if (find(y) == y) f[y] = p;
x = pre[y];
}
}
bool aug(int s) {
rep (i, 0, n) f[i] = i, tp[i] = pre[i] = 0;
tp[que[ql = qr = 1] = s] = 1; // 1: type A ; 2: type B
for (int t = 0; ql <= qr; ) {
int x = que[ql++];
for (int i = h[x], v = to[i]; i; i = ne[i], v = to[i])
if (find(v) == find(x) || tp[v] == 2) continue;
else if (!tp[v]) {
tp[v] = 2, pre[v] = x;
if (!match[v]) {
for (int now = v, last, tmp; now; now = last) {
last = match[tmp = pre[now]];
match[now] = tmp, match[tmp] = now;
} return true;
}
tp[match[v]] = 1, que[++qr] = match[v];
} else if (tp[v] == 1) {
int l = lca(x,v); shrink(x,v,l), shrink(v,x,l);
}
} return false;
}
int main() {
read(n); int x, y;
while (~scanf("%d%d", &x, &y)) add(x,y), add(y,x);
int ans = 0; rep (i, 1, n) ans += (!match[i] && aug(i));
write(ans << 1); puts("");//ans表示有几对
rep (i, 1, n) if (match[i] > i) write(i), putchar(' '), write(match[i]), puts("");
return 0;
}
网络流
最大流
Edmonds-Karp \(O(nm^2)\)
const int N = 2010, M = 20010, inf = 1 << 30;
int h[N], to[M], ne[M], co[M], tot;
int v[N], incf[N], pre[N], s, t, maxflow;
void add(int u, int v, int c) {
ne[++tot] = h[u]; to[h[u] = tot] = v; co[tot] = c;
ne[++tot] = h[v]; to[h[v] = tot] = u; co[tot] = 0;
}
while (cin >> n >> m) {
rep (i, 1, n) h[i] = 0;
s = 1, t = n; tot = 1; maxflow = 0;
rep (i, 1, m) {
int u, v, c; cin >> u >> v >> c;
add(u, v, c);
}
while (bfs()) update();
cout << maxflow << '\n';
}
bool bfs () {
rep (i, 1, n) v[i] = 0;
queue<int> q; q.push(s); v[s] = 1;
incf[s] = inf; //增广路上各边的最小剩余容量
while (!q.empty()) {
int x = q.front(); q.pop(); v[x] = 0;
for (int i = h[x]; i; i = ne[i]) {
if (!co[i]) continue;
int y = to[i];
if (v[y]) continue;
incf[y] = min(incf[x], co[i]);
pre[y] = i; q.push(y); v[y] = 1;
if (y == t) return 1;
}
}
return 0;
}
void update() {
int x = t;
while (x != s) {
int i = pre[x];
co[i] -= incf[t]; co[i ^ 1] += incf[t];
x = to[i ^ 1];
}
maxflow += incf[t];
}
Dinic \(O(n^2m)\)
const int N = 5e4 + 5, M = 3e5 + 5, inf = 1 << 30;
int d[N], s, t, maxflow;
tot = 1;
int flow = 0;
while (bfs()) while (flow = dinic(s, inf)) maxflow += flow;
bool bfs() {
memset(d, 0, sizeof d); memcpy(now, h, sizeof h);
queue<int> q; q.push(s); d[s] = 1;
while (!q.empty()) {
int x = q.front(); q.pop();
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) if (co[i] && !d[y]) {
d[y] = d[x] + 1; q.push(y);
if (y == t) return 1;
}
}
return 0;
}
int dinic(int x, int flow) {
if (x == t) return flow;
int rest = flow, k;
for (int &i = now[x], y = to[i]; i && rest; y = to[i = ne[i]]) if (co[i] && d[y] == d[x] + 1)
if (!(k = dinic(y, min(rest, co[i])))) d[y] = 0;
else co[i] -= k, co[i ^ 1] += k, rest -= k;
return flow - rest;
}
最大流关键边
即参与网络存在\(s\)到\(u\), \(v\)到\(t\)且边\((u, v)\)无流量, 则边\((u, v)\)为关键边, 即从\(s,t\)求bfs可达点
bool vs[N], vt[N];
void dfs(int x, bool *v, bool k) {
v[x] = 1;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
if (co[i ^ k] && !v[y]) dfs(y, v, k);
}
dfs(s, vs, 0); dfs(t, vt, 1);
rep (i, 1, m) k += !co[i << 1] && vs[to[i << 1 | 1]] && vt[to[i << 1]];
费用流
const int N = 5e3 + 5, M = 4e4 + 5, inf = 1 << 30;
int n, m, _, k;
int h[N], to[M], ne[M], co[M], ed[M], tot;
int v[N], incf[N], pre[N], s, t, maxflow, d[N], ans;
void add(int u, int v, int e, int c) {
ne[++tot] = h[u]; to[h[u] = tot] = v; co[tot] = c, ed[tot] = e;
ne[++tot] = h[v]; to[h[v] = tot] = u; co[tot] = -c; ed[tot] = 0;
}
bool bfs() {
rep (i, 1, n) v[i] = 0, d[i] = -inf;
queue<int> q; q.push(s); v[s] = 1; d[s] = 0;
incf[s] = inf; //增广路上各边的最小剩余容量
while (!q.empty()) {
int x = q.front(); q.pop(); v[x] = 0;
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]]) {
if (!ed[i] || d[y] >= d[x] + co[i]) continue;
d[y] = d[x] + co[i]; pre[y] = i;
incf[y] = min(incf[x], ed[i]);
if (!v[y]) q.push(y), v[y] = 1;
}
}
return d[t] != -inf;
}
void update() {
for (int x = t, i = pre[x]; x != s; i = pre[x = to[i ^ 1]])
ed[i] -= incf[t], ed[i ^ 1] += incf[t];
maxflow += incf[t]; ans += d[t] * incf[t];
}
int main() {
IOS; while (cin >> n >> k) { while (bfs()) update(); cout << ans << '\n'; }
return 0;
}
无源汇上下界网络流
int d[N], s, t, now[N], indeg[N], outdeg[N];
int h[N], ne[M], to[M], co[M], tot, ls[M >> 1];
void add(int u, int v, int c) {
ne[++tot] = h[u]; to[h[u] = tot] = v; co[tot] = c;
ne[++tot] = h[v]; to[h[v] = tot] = u; co[tot] = 0;
}
bool bfs() {
memset(d, 0, sizeof d); memcpy(now, h, sizeof h);
queue<int> q; q.push(s); d[s] = 1;
while (!q.empty()) {
int x = q.front(); q.pop();
for (int i = h[x], y = to[i]; i; y = to[i = ne[i]])
if (!d[y] && co[i]) {
d[y] = d[x] + 1; q.push(y);
if (y == t) return 1;
}
}
return 0;
}
int dinic(int x, int flow) {
if (x == t) return flow;
int res = flow, k;
for (int i = now[x], y = to[i]; i && res; now[x] = i, y = to[i = ne[i]])
if (d[y] == d[x] + 1 && co[i])
if (!(k = dinic(y, min(res, co[i])))) d[y] = 0;
else res -= k, co[i] -= k, co[i ^ 1] += k;
return flow - res;
}
int main() {
IOS; cin >> n >> m; tot = 1; s = 0, t = n + 1;
rep (i, 1, m) {
int u, v, x, y; cin >> u >> v >> x >> y;
add(u, v, y - x); ls[i] = x;
indeg[v] += x; outdeg[u] += x;
}
rep (i, 1, n)
if (outdeg[i] > indeg[i]) add(i, t, outdeg[i] - indeg[i]);
else if (outdeg[i] < indeg[i]) add(s, i, indeg[i] - outdeg[i]);
while (bfs()) while (dinic(s, inf));
for (int i = h[s]; i; i = ne[i])
if (co[i]) return cout << "NO", 0;
cout << "YES\n";
rep (i, 1, m) cout << ls[i] + co[i << 1 | 1] << '\n';
return 0;
}
有源汇上下界网络流
把给定的汇点向原点添加一条[\(0\), \(\infty\)]边, 并把给定的原汇点编程普通点, 变成无源汇
有源汇上下界最大流
先按有源汇上下界网络流使得流量平衡, 再把源汇点变为提题目给定的源汇点, 删除汇点间的w无穷边, 跑最大流即可
int main() {
IOS; cin >> n >> m >> S >> T; tot = 1; s = 0, t = n + 1;
rep (i, 1, m) {
int u, v, x, y; cin >> u >> v >> x >> y;
add(u, v, y - x); ls[i] = x;
indeg[v] += x; outdeg[u] += x;
}
add(T, S, inf);
rep (i, 1, n)
if (outdeg[i] > indeg[i]) add(i, t, outdeg[i] - indeg[i]);
else if (outdeg[i] < indeg[i]) add(s, i, indeg[i] - outdeg[i]);
while (bfs()) while (dinic(s, inf));
for (int i = h[s]; i; i = ne[i])
if (co[i]) return cout << "No Solution", 0;
int flow, mxflow = co[m + 1 << 1 | 1]; s = S, t = T;
co[m + 1 << 1] = co[m + 1 << 1 | 1] = 0;
while (bfs()) while (flow = dinic(s, inf)) mxflow += flow;
cout << mxflow;
return 0;
}
有源汇上下界最小流
同有源汇上下界最大流, 只不过变换之后, 是给定的源点变成汇点, 汇点变成源点
将流量退回去, 即给定的源汇点附加的无穷边反边的流量 减去 退回的流量
即最大流榨干参与网络, 最小流退回参与网络
int flow, mxflow = co[m + 1 << 1 | 1]; s = T, t = S;
co[m + 1 << 1] = co[m + 1 << 1 | 1] = 0;
while (bfs()) while (flow = dinic(s, inf)) mxflow -= flow;
cout << mxflow;
字符串
环的最大最小表示,
int get(char s[]) { //先复制一倍
int i = 0, j = 1, k = 0, t;
while (i < len && j < len && k < len) {
t = s[(i + k) % len] - s[(j + k) % len];
if (t == 0) ++k;
else {
if (t > 0) i += k + 1; //最大表示 t < 0
else j += k + 1;
if (i == j) ++j; k = 0;
}
} return i > j ? j : i;
}
字符串哈希
略
ac自动机(trie树)
struct AC {
static const int N = 1e5 + 5, M = 26, C = 'a'; //字符串总长度, 字符范围
int trie[N][M], cnt[N], fail[N], q[N], tot;
vector<VI> idx; //记录节点结尾的字符串id
void init() {
rep (i, 0, M - 1) trie[0][i] = 0;
tot = 0; idx.resize(1, VI());
}
int newnode() {
cnt[++tot] = 0; fail[tot] = 0; memset(trie[tot], 0, sizeof trie[tot]);
return idx.pb(VI()), tot;
}
void insert(char* s, int id) {
int p = 0;
for (int i = 0, ch = s[i] - C; s[i]; p = trie[p][ch], ch = s[++i] - C)
if (!trie[p][ch]) trie[p][ch] = newnode();
++cnt[p]; idx[p].pb(id);
}
void build() {
int head = 0, tail = -1;
rep (i, 0, M - 1) if (trie[0][i]) q[++tail] = trie[0][i];
for (int p = q[head]; head <= tail; p = q[++head]) rep (i, 0, M - 1)
if (trie[p][i])
fail[trie[p][i]] = trie[fail[p]][i], q[++tail] = trie[p][i];
else trie[p][i] = trie[fail[p]][i];
}
int query(char* s) {
set<int> vis; int res = 0;
for (int i = 0, p = trie[0][s[i] - C]; s[i]; p = trie[p][s[++i] - C])
for (int tmp = p; tmp && !vis.count(tmp); tmp = fail[tmp])
res += cnt[tmp], vis.insert(tmp);
return res;
}
} ac;
kmp
char t[LenT], s[LenS];
int lens, lent, cnt, f[LenT], extend[LenS];
void KMP() { //可根据f数组建立出sam的子集自动机(f[i]是endpos等价类子集)
for (int i = 2, j = f[1] = 0; i <= lent; ++i) {
while (j > 0 && t[i] != t[j + 1]) j = f[j];
if (t[i] == t[j + 1]) ++j; f[i] = j;
}
}
void ext_KMP() { //也可以直接 t = t + '#' + s; 直接kmp也行
for (int i = 1, j = extend[1] = 0; i <= lens; ++i) {
while (j > 0 && (j == lent || s[i] != t[j + 1])) j = f[j];
extend[i] = s[i] == t[j + 1] ? ++j : j; cnt += (j == lent)
}
}
Z函数(扩展KMP)
int lens, lent, f[N], extend[N];
char s[N], t[N];
void kmp(char* t, int lent) { //t从1开始
int j = 0, k = 2;
while (j + 2 <= lent && t[j + 1] == t[j + 2]) ++j;
f[2] = j; f[1] = lent;
for (int i = 3, p = k + f[k] - 1; i <= lent; ++i, p = k + f[k] - 1)
if (i + f[i - k + 1] - 1 < p) f[i] = f[i - k + 1];
else {
j = max(0, p - i + 1);
while (j + i <= lent && t[j + 1] == t[i + j]) ++j;
f[i] = j; k = i;
}
}
void ex_kmp(char *s, char *t, int lens, int lent) { //s, t下标都是从1开始
int j = 0, k = 1;
while (j + 1 <= min(lens, lent) && s[j + 1] == t[j + 1]) ++j;
extend[1] = j;
for (int i = 2, p = k + extend[k] - 1; i <= lens; ++i, p = k + extend[k] - 1)
if (i + f[i - k + 1] - 1 < p) extend[i] = f[i - k + 1];
else {
j = max(0, p - i + 1);
while (j + i <= lens && j + 1 <= lent && t[j + 1] == s[i + j]) ++j;
extend[i] = j; k = i;
}
}
int main() {
IOS; cin >> s + 1 >> t + 1;
lent = strlen(t + 1); lens = strlen(s + 1);
kmp(t, lent); ex_kmp(s, t, lens, lent);
rep (i, 1, lens) cout << extend[i] << ' ';
return 0;
}
manachar
int pArr[N << 1];
char s[N], chaArr[N << 1];
int maxLcsplength(char *s) {
int len = 0, R = -1, C = -1, maxN = 0; chaArr[len++] = '$', chaArr[len++] = '#';
for (register int i = 0; s[i]; ++i) chaArr[len++] = s[i], chaArr[len++] = '#';
chaArr[len] = '\0';
for (register int i = 0; i < len; ++i) {
pArr[i] = R > i ? min(R - i, pArr[(C << 1) - i]) : 1;
while (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) ++pArr[i];
if (i + pArr[i] > R) R = i + pArr[i], C = i; maxN = max(maxN, pArr[i]);
} return maxN - 1;
}
序列自动机
struct SqAM {
static const int N = 2e3 + 5, M = 26, C = 'a';
struct Node { int fa, ne[26]; } tr[N << 1];
int rt, tot, lst[M];
int newNode() { return memset(tr[++tot].ne, 0, sizeof tr[0].ne), tot; }
void init() { tot = 0; rep (i, 0, M - 1) lst[i] = 1; rt = newNode(); }
void insert(int ch) {
int p = lst[ch], cur = newNode(); tr[cur].fa = p;
rep (i, 0, M - 1) for (int j = lst[i]; j && !tr[j].ne[ch]; j = tr[j].fa)
tr[j].ne[ch] = cur;
lst[ch] = cur;
}
void build(char* s) { for (int i = 0; s[i]; insert(s[i++] - C)); }
bool find(char* s) {
int p = 1;
for (int i = 0; p && s[i]; p = tr[p].ne[s[i++] - C]);
return p;
}
};
后缀
前缀在整个串中出现的次数, sam处理完后遍历原串输出cnt即可
重复可重叠最长子串 自动机(max tr[tr[i].fa].len)
重复不可重叠最长子串 数组(二分, rk连续的一段且长度>=mid, 这段rk连续的sa位置最大最小值>mid)
重复k次的可重叠最长子串 自动机(cnt计数, 给fa节点打标记, 在cnt >= k && fa 节点取max len)
子串个数(相同字串不同位置算1/多个) 自动机
n个字串lca, sam上跑n次match, 取max ans[i]
两个后缀最长的lca,两个节点在parent树上的lca
重复次数最多的连续重复子串(某子串在某个长子串中不重叠出现次数最多, 求的是这个长子串),后缀数组
void solve() {
Max = 0;
for (int i = 1; i <= a.len; ++i)
for (int j = 1; j + i <= a.len; j += i) {
ans = a.rmq_query(j, j + i); k = j - (i - ans % i); ans = ans / i + 1;
if (k >= 1 && a.rmq_query(k, k + i) >= i) ++ans;
if (Max < ans) Max = ans, cnt = 0, q[cnt++] = i;
else if (Max == ans && i != q[cnt - 1]) q[cnt++] = i;
}
//输出字典序最小的
for (int i = 1; i <= a.len; ++i)
for (int j = 0; j < cnt; ++j)
if (a.rmq_query(a.sa[i], a.sa[i] + q[j]) >= q[j] * (Max - 1)) {
a.s[a.sa[i] + q[j] * Max] = '\0';
printf("%s\n", a.s + a.sa[i]); return;
}
}
后缀自动机
struct SAM { //不管是不是多组数据都调用init
static const int N = 5e5 + 5, M = 26, C = 'a';
struct node { int fa, len, ne[M]; } tr[N << 1];
int sz, las, len, c[N], rk[N << 1], cnt[N << 1];//(i~len)有cnt[i]个字母a[i]
int sum[N << 1]; //排名为i的节点为头包含的字串数量
int ans[N << 1], f[N << 1];
void init() {
rep (i, 1, sz)
tr[i].len = tr[i].fa = c[i] = 0, memset(tr[i].ne, 0, sizeof tr[i].ne);
sz = las = 1; len = 0;
}
void add(int ch) {
int p = las, cur = las = ++sz;
tr[cur].len = tr[p].len + 1; ++cnt[cur];
for (; p && !tr[p].ne[ch]; p = tr[p].fa) tr[p].ne[ch] = cur;
if (p == 0) { tr[cur].fa = 1; return; }
int q = tr[p].ne[ch];
if (tr[q].len == tr[p].len + 1) { tr[cur].fa = q; return; }
int nq = ++sz; tr[nq] = tr[q]; tr[nq].len = tr[p].len + 1;
for (; p && tr[p].ne[ch] == q; p = tr[p].fa) tr[p].ne[ch] = nq;
tr[q].fa = tr[cur].fa = nq;
}
void build(char *s) {
for (int& i = len; s[i]; ++i) add(s[i] - C);
}
void sort() {
rep (i, 1, sz) c[i] = 0;
rep (i, 1, sz) ++c[tr[i].len];
rep (i, 1, len) c[i] += c[i - 1];
rep (i, 1, sz) rk[c[tr[i].len]--] = i;
}
void getSizeLen(bool f) {
per (i, sz, 2) //未考虑被压缩的字串(只出现过1次, 且不是原串的前缀)
if (!f) cnt[rk[i]] = 1; //不同位置的相同字串算一个
else cnt[tr[rk[i]].fa] += cnt[rk[i]]; //不同位置的相同字串算多个
per (i, sz, 1) { //忽略tr[1]的大小
sum[rk[i]] = i == 1 ? 0 : cnt[rk[i]];
rep (j, 0, M - 1) if (tr[rk[i]].ne[j]) sum[rk[i]] += sum[tr[rk[i]].ne[j]];
}
}
//t匹配s每个位置最大长度, 求多个串再此位置的最大值,就umin(ans[i],f[i]), 初始化ans=max
int match(char *s) {
int lenx = 0, p = 1, tmp = 0, mx = 0;
memset(f, 0, sizeof f);
for(int& i = lenx, ch = s[i] - C; s[i]; ch = s[++i] - C) {
if (tr[p].ne[ch]) p = tr[p].ne[ch], ++tmp;
else {
while (!tr[p].ne[ch] && p) p = tr[p].fa;
if(p == 0) p = 1, tmp = 0;
else tmp = tr[p].len + 1, p = tr[p].ne[ch];
} umax(f[p], tmp);
}
per (i, sz, 1) { p = tr[rk[i]].fa; umax(f[p], min(f[rk[i]], tr[p].len)); }
rep (i, 2, sz) umin(ans[i], f[i]), umax(mx, ans[i]);
return mx;
}
void dfssub(int u) { //每个节点可以向下延申字串的数量,考虑被压缩的字串
if (sum[u]) return; sum[u] = 1;
for (int i = 0, v; i < M; ++i)
if (v = tr[u].ne[i]) dfssub(v), sum[u] += sum[v];
}
//寻找字串中排名第x的字串,直接进来1, tr[1]空串排名0
int kth(int k, char *s) {
//memset(f, 0, sizoe f);
if (sum[1] < k) return s[0] = '\0', 0;
int cur = 1, len = 0;
while (k) rep (i, 0, M - 1) if (tr[cur].ne[i]) {
int v = tr[cur].ne[i];
if (sum[v] >= k) { s[len++] = C + i; cur = v; k -= cnt[v]; break; }
else k -= sum[v];
} return s[len] = '\0', len;
}
} sam;
后缀数组
struct SA {
static const int N = 30000 + 9;
char str[N]; //sa[i]表示排名i的后缀起始下标,rk[i]表示起始下标i后缀的排名
int sa[N], rk[N], tp[N], tax[N], lcp[N], len, f[N][30], M, lg[N];
inline void sort() {
memset(tax, 0, (M + 1) * sizeof(int));
rep (i, 1, len) ++tax[rk[i]];
rep (i, 1, M) tax[i] += tax[i - 1];
per (i, len, 1) sa[tax[rk[tp[i]]]--] = tp[i];
}
void getH() {
for (int i = 1, j, k = 0; i <= len; lcp[rk[i++]] = k) {
if (k) --k; j = sa[rk[i] - 1];
while (str[i + k] == str[j + k]) ++k;
}
}
void SuffixSort() { //字符串下标从1开始
M = 200; len = 1;
for (int& i = len; str[i]; ++i) rk[i] = str[i], tp[i] = i;
--len; sort();
for (int w = 1, p = 0; p < len; w <<= 1, M = p) {
p = 0;
rep (i, 1, w) tp[++p] = len - w + i;
rep (i, 1, len) if (sa[i] > w) tp[++p] = sa[i] - w;
sort(); swap(tp, rk); rk[sa[1]] = p = 1;
rep (i, 2, len)
rk[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]]
&& tp[sa[i - 1] + w] == tp[sa[i] + w]) ? p : ++p;
} getH();
}
void rmq_init() {
memset(f, 63, sizeof f); lg[1] = 0;
rep (i, 2, len) lg[i] = lg[i + 1 >> 1] + 1;
rep (i, 0, len - 1) f[i][0] = lcp[i + 1];
for (int j = 1, mj = 2; mj <= len; ++j, mj <<= 1) rep (i, 1, len - mj)
f[i][j] = min(f[i][j - 1], f[i + (mj >> 1)][j - 1]);
}
int rmq_query(int l, int r) {
if (l < 1 || r > len) return 0;
if (l == r) return len - l + 1;
l = rk[l], r = rk[r];
if (l > r) swap(l, r);
return min(f[l][lg[r - l] - 1], f[r - k][lg[r - l] - 1]);
}
} sa;
树上SA
struct SA { //树上后缀数组要用倍增, 叶子节点到根节点是一个字符串, 跟一般不同
static const int N = 5e5 + 9;
char str[N]; //sa[i]表示排名i的后缀起始下标,rk[i]表示起始下标i后缀的排名
int sa[N], rk[N], tp[N], tax[N], len;
int rk2[N], rkk[N]; //树上sa新增
inline void sort(int* sa, int* rk, int* tp, int M) { //要排两次序
memset(tax, 0, (M + 1) * sizeof(int));
rep(i, 1, len) ++tax[rk[i]];
rep(i, 1, M) tax[i] += tax[i - 1];
per(i, len, 1) sa[tax[rk[tp[i]]]--] = tp[i];
}
void SuffixSort() { //俩个串相同比较父亲串,再比较这俩串的节点
int p; len = 1;
for (int& i = len; str[i]; ++i) rk2[i] = str[i] - 'a' + 1, tp[i] = i;
--len; sort(sa, rk2, tp, 30); rk[sa[1]] = rkk[sa[1]] = p = 1;
rep(i, 2, len) {
rk[sa[i]] = rk2[sa[i - 1]] == rk2[sa[i]] ? p : ++p;
rkk[sa[i]] = i;
}
for (int w = 1, t = 0; w < len; w <<= 1, ++t) {
rep(i, 1, len) rk2[i] = rkk[ST.f[i][t]];
sort(tp, rk2, sa, len); sort(sa, rk, tp, p);
swap(rk, tp); rk[sa[1]] = rkk[sa[1]] = p = 1;
rep(i, 2, len) {
rk[sa[i]] = tp[sa[i - 1]] == tp[sa[i]]
&& tp[ST.f[sa[i - 1]][t]] == tp[ST.f[sa[i]][t]] ? p : ++p;
rkk[sa[i]] = i;
}
}
rep(i, 1, len) rk[i] = rkk[i];
}
} sa;
广义后缀自动机
离线
struct EXSAM { //trie一样的空间 N * M, 再建自动机为 N * M << 1
static const int N = 1e5 + 5, M = 26, C = 'a', NUM = 15;//N字串总长度,NUM字符串个数
struct Node { int len, fa, ne[M]; } tr[N << 1]; //根据情况增大N,把c数组删了
int tot, mxlen, curString, cnt[N << 1][NUM], tax[N << 1], rk[N << 1];
int newNode() { return memset(tr[++tot].ne, 0, sizeof tr[0].ne), tot; }
void init() {
memset(tr[0].ne, 0, sizeof tr[0].ne);
tot = mxlen = 0; tr[0].fa = -1;
} //离线并且要对多个字串公共操作, 很难将数组cnt开好,只给你总长不给NUM很难受,在线好
int insertSAM(int las, int ch) {
int cur = tr[las].ne[ch], p = tr[las].fa;
tr[cur].len = tr[las].len + 1;
for (; p != -1 && !tr[p].ne[ch]; p = tr[p].fa) tr[p].ne[ch] = cur;
if (p == -1) return tr[cur].fa = 0, cur;
int q = tr[p].ne[ch];
if (tr[p].len + 1 == tr[q].len) return tr[cur].fa = q, cur;
int nq = ++tot; tr[nq].len = tr[p].len + 1; tr[nq].fa = tr[q].fa;
rep (i, 0, M - 1) tr[nq].ne[i] = tr[tr[q].ne[i]].len ? tr[q].ne[i] : 0;
for (; p != -1 && tr[p].ne[ch] == q; p = tr[p].fa) tr[p].ne[ch] = nq;
return tr[cur].fa = tr[q].fa = nq, cur;
}
int insertTrie(int cur, int ch) {
if (!tr[cur].ne[ch]) tr[cur].ne[ch] = newNode();
++cnt[tr[cur].ne[ch]][curString];
return tr[cur].ne[ch];
}
void insert(const string& s) {
for (int i = 0, p = 0; i < s.size(); p = insertTrie(p, s[i++] - C));
umax(mxlen, s.size()); ++curString; //curString下标从0开始
}
void insert(const char *s) {
int len = 0;
for (int& i = len, p = 0; s[i]; p = insertTrie(p, s[i++] - C));
umax(mxlen, len - 1); ++curString; //curString下标从0开始
}
void build() {
queue<pair<int, int>> q;
rep (i, 0, M - 1) if (tr[0].ne[i]) q.push({ i, 0 });
while (!q.empty()) {
PII it = q.front(); q.pop();
int las = insertSAM(it.se, it.fi);
rep (i, 0, M - 1) if (tr[las].ne[i]) q.push({ i, las });
}
}
void sort() {
rep (i, 1, tot) tax[i] = 0;
rep (i, 1, tot) ++tax[tr[i].len];
rep (i, 2, mxlen) tax[i] += tax[i - 1];
rep (i, 1, tot) rk[tax[tr[i].len]--] = i;
}
void getSizeLen() {
per(i, tot, 1)rep(j, 0, curString - 1)cnt[tr[rk[i]].fa][j] += cnt[rk[i]][j];
}
int mxComlca() { //最长公共子串
int ans = 0;
for (int i = 0, flag = 1; i <= tot; ++i) {
rep (j, 0, curString - 1) if (!cnt[i][j]) { flag = 0; break; }
if (flag) umax(ans, tr[i].len);
}
return ans;
}
ll difsub() { //不同位置相同字串算1个
ll ans = 0;
rep (i, 1, tot) ans += tr[i].len - tr[tr[i].fa].len;
return ans;
}
int tag[N << 1], cnt[N];//cnt记录每个节点经过多少个字串
void dfs(int p, int id) {
for (; p != -1 && tag[p] != id; p = tr[p].fa) tag[p] = id, ++cnt[p];
}
void cntLen(char *s, int* len, int n) {
rep (i, 1, n)
for(int j=len[i-1],p=tr[0].ne[s[j]-C];j<len[i];p=tr[p].ne[s[++j]-C])dfs(p,i);
} //s存n个字串,len第i个字串结尾的位置
} exSam;
回文自动机
struct PAM { //当len[i] > 0才是真实串(len[0]=0,len[1]=-1)
static const int N = 260817, M = 26, C = 'a';
struct Node { int ne[M], len, fail, cnt; } tr[N];
int sz, tot, last;
char s[N];
int newNode(int l) {
memset(tr[++sz].ne, 0, sizeof(tr[0].ne)); tr[sz].len = l;
return tr[sz].fail = tr[sz].cnt = 0, sz;
}
void init() {
sz = -1; last = 0; s[tot = 0] = '$';
newNode(0); newNode(-1); tr[0].fail = 1;
}
int getfail(int x) {
while (s[tot - tr[x].len - 1] != s[tot]) x = tr[x].fail;
return x;
}
void insert(char c) {
s[++tot] = c; int now = getfail(last), ch = c - C;
if (!tr[now].ne[ch]) {
int x = newNode(tr[now].len + 2);
tr[x].fail = tr[getfail(tr[now].fail)].ne[ch];
tr[now].ne[ch] = x;
}
++tr[last = tr[now].ne[ch]].cnt;
}
void build(char *s) {
for (int i = 0; s[i]; ++i) insert(s[i]);
per (i, sz, 0) tr[tr[i].fail].cnt += tr[i].cnt;
}
ll solve() {
ll ans = 0;
rep (i, 1, sz) umax(ans, (ll)tr[i].len * tr[i].cnt);
return ans;
}
} pam;
Dancing linke
行为决策, 列为影响
数独
先考虑决策是什么。
在这一题中,每一个决策可以用形如\((r,c,w)\)的有序三元组表示。
注意到“宫”并不是决策的参数,因为它可以被每个确定的\((r,c)\)表示。
因此有\(9 \times 9 \times 9 = 729\)行。
再考虑状态是什么。
我们思考一下\((r,c,w)\)这个决将会造成什么影响。记\((r,c)\)所在的宫为\(b\)。
- 第\(r\)行用了一个\(w\)(用\(9 \times 9 = 81\)列表示);
- 第\(c\)列用了一个\(w\)(用\(9 \times 9 = 81\)列表示);
- 第\(b\)宫用了一个 (用\(9 \times 9 = 81\)列表示);
- \((r,c)\)中填入了一个数(用\(9 \times 9 = 81\)列表示)。
因此有\(81 * 4 = 324\)列,共\(729 \times 4 = 2916\)个\(1\)。
至此,我们成功地将\(9 \times 9\)的数独问题转化成了一个 有\(729\)行,\(324\)列,共\(2916\)个\(1\)的精确覆盖问题。
#include <bits/stdc++.h>
using namespace std;
struct DLX {
static const int N = 1e5 + 5;
#define IT(i, A, x) for(int i=A[x];i^x;i=A[i])
int n, m, tot, first[N], siz[N], stk[N], ans;
int L[N], R[N], U[N], D[N], col[N], row[N];
void build(const int &r, const int &c) {
for (int i = 0; i <= c; ++i) L[i] = i - 1, R[i] = i + 1, U[i] = D[i] = i;
n = r, m = c; L[0] = c, R[c] = 0, tot = c;
memset(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz));
}
void insert(const int &r, const int &c) {
col[++tot] = c, row[tot] = r, ++siz[c];
D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot;
if (!first[r]) first[r] = L[tot] = R[tot] = tot;
else {
R[tot] = R[first[r]], L[R[first[r]]] = tot;
L[tot] = first[r], R[first[r]] = tot;
}
}
void remove(const int &c) {
L[R[c]] = L[c], R[L[c]] = R[c];
IT(i, D, c) IT(j, R, i) U[D[j]] = U[j], D[U[j]] = D[j], --siz[col[j]];
}
void recover(const int &c) {
IT(i, U, c) IT(j, L, i) U[D[j]] = D[U[j]] = j, ++siz[col[j]];
L[R[c]] = R[L[c]] = c;
}
bool dance(int dep) {
if (!R[0]) return ans = dep, 1;
int c = R[0];
IT(i, R, 0) if (siz[i] < siz[c]) c = i;
remove(c);
IT(i, D, c) {
stk[dep] = row[i];
IT(j, R, i) remove(col[j]);
if (dance(dep + 1)) return 1;
IT(j, L, i) recover(col[j]);
}
recover(c);
return 0;
}
#undef IT
} dlx;
int a[10][10];
void insert(int r, int c, int n) {
int g = (r - 1) / 3 * 3 + (c - 1) / 3 + 1;
int id = (r - 1) * 81 + (c - 1) * 9 + n;
dlx.insert(id, (r - 1) * 9 + n);
dlx.insert(id, 81 + (c - 1) * 9 + n);
dlx.insert(id, 162 + (g - 1) * 9 + n);
dlx.insert(id, 243 + (r - 1) * 9 + c);
}
int main() {
string s;
while (cin >> s, s != "end") {
dlx.build(729, 324);
for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) {
a[i][j] = s[(i - 1) * 9 + j - 1] == '.' ? 0 : s[(i - 1) * 9 + j - 1] ^ '0';
for (int v = 1; v <= 9; ++v) if (!a[i][j] || a[i][j] == v) insert(i, j, v);
}
dlx.dance(0);
for (int i = 0; i < dlx.ans; ++i)
a[(dlx.stk[i] - 1) / 81 + 1][(dlx.stk[i] - 1) / 9 % 9 + 1] = (dlx.stk[i] - 1) % 9 + 1;
for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) cout << a[i][j]; cout << '\n';
}
return 0;
}
靶形数独
这一题与数独的模型构建 一模一样,主要区别在于答案的更新。
这一题可以开一个权值数组,每次找到一组数独的解时,
每个位置上的数乘上对应的权值计入答案即可。
#include <bits/stdc++.h>
using namespace std;
struct DLX {
static const int N = 1e5 + 5;
#define IT(i, A, x) for(int i=A[x];i^x;i=A[i])
int n, m, tot, first[N], siz[N], stk[N], ans;
int L[N], R[N], U[N], D[N], col[N], row[N], mx, w[N];
void build(const int &r, const int &c) {
for (int i = 0; i <= c; ++i) L[i] = i - 1, R[i] = i + 1, U[i] = D[i] = i;
n = r, m = c; L[0] = c, R[c] = 0, tot = c;
memset(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz));
}
void insert(const int &r, const int &c, const int &W) {
col[++tot] = c, row[tot] = r, w[tot] = W, ++siz[c];
D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot;
if (!first[r]) first[r] = L[tot] = R[tot] = tot;
else {
R[tot] = R[first[r]], L[R[first[r]]] = tot;
L[tot] = first[r], R[first[r]] = tot;
}
}
void remove(const int &c) {
L[R[c]] = L[c], R[L[c]] = R[c];
IT(i, D, c) IT(j, R, i) U[D[j]] = U[j], D[U[j]] = D[j], --siz[col[j]];
}
void recover(const int &c) {
IT(i, U, c) IT(j, L, i) U[D[j]] = D[U[j]] = j, ++siz[col[j]];
L[R[c]] = R[L[c]] = c;
}
bool dance(int dep, int cur) {
if (!R[0]) return mx = max(cur, mx), 1;
int c = R[0];
IT(i, R, 0) if (siz[i] < siz[c]) c = i;
remove(c);
IT(i, D, c) {
stk[dep] = row[i];
IT(j, R, i) remove(col[j]);
dance(dep + 1, cur + w[i]);
IT(j, L, i) recover(col[j]);
}
recover(c);
return 0;
}
#undef IT
} dlx;
int a[10][10];
void insert(int r, int c, int n, int w) {
int g = (r - 1) / 3 * 3 + (c - 1) / 3 + 1;
int id = (r - 1) * 81 + (c - 1) * 9 + n;
dlx.insert(id, (r - 1) * 9 + n, w);
dlx.insert(id, 81 + (c - 1) * 9 + n, w);
dlx.insert(id, 162 + (g - 1) * 9 + n, w);
dlx.insert(id, 243 + (r - 1) * 9 + c, w);
}
int main() {
dlx.build(729, 324);
for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) {
cin >> a[i][j]; int d = max(abs(i - 5), abs(j - 5));
for (int v = 1; v <= 9; ++v) if (!a[i][j] || a[i][j] == v) insert(i, j, v, (10 - d) * v);
}
dlx.dance(0, 0); cout << (dlx.mx ? dlx.mx : -1);
return 0;
}