2023ACM暑假集训
杭电多效联赛
ExCRT
题解:数据范围很小(\(O(n^2)\)能过),考虑暴力枚举两条树上路径后,转化为线性同余方程,利用excrt求解即可。
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 6e3 + 10, inf = 0x3f3f3f3f;
int Exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1;
y = 0;
return a;
}
int d = Exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
}
inline int ExCRT(const vector<int>&a, const vector<int>&m) {
int n = a.size();
int nowa = *a.begin(), nowm = *m.begin();
for(int i = 1, aa, bb, cc, x, y, d; i < n; i++) {
aa = nowm;
bb = m[i];
cc = ((a[i] - nowa)%m[i] + m[i]) % m[i];
d = Exgcd(aa, bb, x, y);
//无解返回 -1
if(cc % d) {
return -1;
}
int mod = nowm * (bb / d);
// 龟速乘
x = (x % mod + mod) * (cc / d) % mod;
nowa = ((nowm * x % mod) + nowa) % mod;
nowm = mod;
}
return (nowa+nowm) % nowm;
}
//求最小的正整数 mod n = x, mod m = y
int calc(int n, int m, int x, int y) {
vector<int> a({x, y});
vector<int> b({n, m});
int res = ExCRT(a, b);
if (res == -1) return inf;
return res;
}
vector<int> g[N];
int n, m, d[N], f[N], vis[N];
void dfs(int u, int fa) {
d[u] = d[fa] + 1;
f[u] = fa;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
}
}
void getpath(int x, int y, vector<int> &path) {
vector<int> tmp;
while (d[x] > d[y]) path.push_back(x),x = f[x];
while (d[y] > d[x]) tmp.push_back(y),y = f[y];
while (x != y) {
path.push_back(x),x = f[x];
tmp.push_back(y),y = f[y];
}
path.push_back(x);
while (!tmp.empty()) path.push_back(tmp.back()), tmp.pop_back();
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) g[i].clear(), d[i] = f[i] = vis[i] = 0;
for (int i = 1; i <= n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
while (m--) {
int sa, ta, sb, tb;
cin >> sa >> ta >> sb >> tb;
vector<int> patha, pathb;
getpath(sa, ta, patha);
getpath(sb, tb, pathb);
int lena = patha.size(), lenb = pathb.size();
memset(vis, -1, sizeof(int) * (n + 1));
vector<pii> p;
for (int i = 0; i < lena; i++) {
vis[patha[i]] = i;
}
for (int i = 0; i < lenb; i++) {
if (vis[pathb[i]] != -1) {
int a1 = vis[pathb[i]], a2 = 2 * lena - 2 - a1;
int b1 = i, b2 = 2 * lenb - 2 - b1;
if (a1 == 0 || a1 == lena - 1) a2 = a1;
if (b1 == 0 || b1 == lenb - 1) b2 = b1;
p.emplace_back(a1, b1);
p.emplace_back(a1, b2);
p.emplace_back(a2, b1);
p.emplace_back(a2, b2);
}
}
if (p.empty()) {
cout << "-1\n";
continue;
}
int ans = inf;
for (auto u : p) {
ans = min(ans, calc(lena * 2 - 2, lenb * 2 - 2, u.first, u.second));
}
if (ans == inf) {
cout << "-1\n";
continue;
}
ans %= (2 * lena - 2);
if (ans > lena - 1) cout << patha[2 * lena - 2 - ans] << "\n";
else cout << patha[ans] << "\n";
}
}
signed main() {
//ios::sync_with_stdio(false); cin.tie(0);
int T; cin >> T;
while (T--) {
solve();
}
return 0;
}
分块卡常/读入优化/卡时间
毒瘤题
// 这份代码本机测试14s
//
// Created by blackbird on 2023/7/25.
//
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define max(a,b) (b>a?b:a)
// #define DEBUG 1 // 调试开关
struct IO {
#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')
char buf[MAXSIZE], *p1, *p2;
char pbuf[MAXSIZE], *pp;
#if DEBUG
#else
IO() : p1(buf), p2(buf), pp(pbuf) {}
~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endif
char gc() {
#if DEBUG // 调试,可显示字符
return getchar();
#endif
if (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);
return p1 == p2 ? ' ' : *p1++;
}
bool blank(char ch) {
return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
template <class T>
void read(T &x) {
double tmp = 1;
bool sign = 0;
x = 0;
char ch = gc();
for (; !isdigit(ch); ch = gc())
if (ch == '-') sign = 1;
for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');
if (ch == '.')
for (ch = gc(); isdigit(ch); ch = gc())
tmp /= 10.0, x += tmp * (ch - '0');
if (sign) x = -x;
}
void read(char *s) {
char ch = gc();
for (; blank(ch); ch = gc())
;
for (; !blank(ch); ch = gc()) *s++ = ch;
*s = 0;
}
void read(char &c) {
for (c = gc(); blank(c); c = gc())
;
}
void push(const char &c) {
#if DEBUG // 调试,可显示字符
putchar(c);
#else
if (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;
*pp++ = c;
#endif
}
template <class T>
void write(T x) {
if (x < 0) x = -x, push('-'); // 负数输出
static T sta[35];
T top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) push(sta[--top] + '0');
}
template <class T>
void write(T x, char lastChar) {
write(x), push(lastChar);
}
} io;
const int N = 5e5 + 10;
const int siz = 11;//块大小
const int M = 1 << siz;
int n, a[N][6];
inline bool ge(int x, int y) { // x >= y
for (int i = 0; i < 5; i++) {
if (a[x][i] < a[y][i]) return false;
}
return true;
}
int f[N], mx[M];
int lt[5][siz], val[5][siz], rk[5][siz];
signed main() {
auto st = clock();
freopen("1002.in", "r", stdin);
//freopen("1002out.txt", "w", stdout);
//cin.tie(nullptr)->sync_with_stdio(false); srand(time(0));
int T = 1; io.read(T);//cin >> T;
while (T--) {
//auto start = clock();
io.read(n);//cin >> n;
const int m = n / siz + (n % siz != 0);
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 6; j++) io.read(a[i][j]);
f[i] = a[i][5];
}
int l = 1 - siz, r = 0;
for (int blk = 1; blk <= m; blk++) {
l += siz; r += siz;
if (r > n) r = n;
const int len = r - l + 1, Max = (1 << (r - l)) - 1;
for (int i = l; i <= r; i++) {
for (int j = i + 1; j <= r; j++) {
if (ge(j, i)) {
f[j] = max(f[j], f[i] + a[j][5]);
}
}
}
for (int i = 0; i <= Max; i++) {
mx[i] = 0;
for (int j = 0; j < len; j++) {
if (i >> j & 1) {
mx[i] = max(mx[i], f[l + j]);
}
}
}
//vector<int> val[5], rk[5];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < len; j++) {
val[i][j] = a[l + j][i];
}
sort(val[i], val[i] + len + 1);
for (int j = 0; j < len; j++) {
for (int k = 0; k < len; k++) {
if (a[l + j][i] == val[i][k]) {
rk[i][j] = k;
break;
}
}
}
}
memset(lt, 0, sizeof lt);
for (int i = 0; i < 5; i++) {
for (int k = 0; k < len; k++) {
for (int j = 0; j < len; j++) {
if (rk[i][j] <= k) {
lt[i][k] |= (1 << j);
}
}
}
}
for (int i = r + 1; i <= n; i++) {
int ok = Max;
for (int j = 0; j < 5; j++) {
int k;
for (k = len - 1; k >= 0; k--) {
if (val[j][k] <= a[i][j]) {
break;
}
}
if (k < 0) {
ok = 0;
break;
}
ok &= lt[j][k];
}
f[i] = max(f[i], mx[ok] + a[i][5]);
}
//auto end = clock();
//if ((double)(end-start)/CLOCKS_PER_SEC * 1000 > 1984) break;
}
// for (int i = 1; i <= n; i++) {
// io.write(f[i],'\n');///cout << f[i] << "\n";
// }
}
auto ed = clock();
cout << (double)(ed-st)/CLOCKS_PER_SEC << "\n";
return 0;
}
类似替罪羊树实现的kd-tree
由leven的好朋友提供。常数很小,可以通过。(还写什么分块bitset!)
// by Isaunoya
#include<bits/stdc++.h>
using namespace std;
int f = 0, flag = 0;
const int K = 6;
const int maxn = 1e5 + 51;
struct point {
int d[K], w;
bool operator < (const point& p) const {
if(d[f] ^ p.d[f]) { return d[f] < p.d[f]; }
for(int i = 0 ; i < K - flag ; i ++) {
if(d[i] ^ p.d[i]) { return d[i] < p.d[i]; }
}
return 0;
}
};
void cmax(int&x, const int&y) {
if(x < y) x = y;
}
void cmin(int&x, const int&y) {
if(x > y) x = y;
}
const double alpha = 0.75;
template < short K >
struct KDT {
#define ls son[0]
#define rs son[1]
struct tree {
int sz, mxw;
tree *son[2];
point range, mn, mx;
void up() {
sz = ls -> sz + rs -> sz + 1;
mxw = range.w;
cmax(mxw, ls -> mxw);
cmax(mxw, rs -> mxw);
mx = mn = range;
for(int i = 0 ; i < K ; i ++) {
cmin(mn.d[i], ls -> mn.d[i]);
cmin(mn.d[i], rs -> mn.d[i]);
cmax(mx.d[i], ls -> mx.d[i]);
cmax(mx.d[i], rs -> mx.d[i]);
}
}
bool unb() {
return ls -> sz > sz * alpha || rs -> sz > sz * alpha;
}
bool in(point a, point b) {
for(int i = 0 ; i < K ; i ++)
if(a.d[i] > mn.d[i] || b.d[i] < mx.d[i])
return 0;
return 1;
}
bool out(point a, point b) {
for(int i = 0 ; i < K ; i ++)
if(a.d[i] > mx.d[i] || b.d[i] < mn.d[i])
return 1;
return 0;
}
bool at(point a, point b) {
for(int i = 0 ; i < K ; i ++)
if(range.d[i] < a.d[i] || range.d[i] > b.d[i])
return 0;
return 1;
}
} pool[maxn], * tail, * null, * root, * rec[maxn];
int top;
point down;
void init() {
tail = pool;
null = tail ++;
for(int i = 0 ; i < K ; i ++)
null -> mn.d[i] = 1e18;
for(int i = 0 ; i < K ; i ++)
null -> mx.d[i] = -1e18;
down = null -> mx;
null -> son[0] = null -> son[1] = null;
root = null;
}
tree * newnode(point x) {
tree *p = top ? rec[top --] : tail ++;
p -> son[0] = p -> son[1] = null;
p -> range = p -> mn = p -> mx = x;
p -> mxw = x.w, p -> sz = 1;
return p;
}
point b[maxn];
int cnt;
void trv(tree *p) {
if(p == null) return;
trv(p -> ls);
b[++ cnt] = p -> range;
rec[++ top] = p;
trv(p -> rs);
}
tree * build(int l, int r, int d) {
if(l > r) return null;
int mid = l + r >> 1;
f = d;
nth_element(b + l, b + mid, b + r + 1);
tree * p = newnode(b[mid]);
if(l == r) return p;
p -> ls = build(l, mid - 1, (d + 1) % K);
p -> rs = build(mid + 1, r, (d + 1) % K);
p -> up(); return p;
}
void rebld(tree*&p) {
cnt = 0, trv(p), p = build(1, cnt, 0);
}
int ans;
void qry(tree*p, point a, point b) {
if(p == null) return;
if(p -> out(a, b)) return;
if(p -> in(a, b)) {
cmax(ans, p -> mxw);
return;
}
cmax(ans, p -> at(a, b)? p -> range.w : 0);
if(p -> ls -> mxw > ans) qry(p -> ls, a, b);
if(p -> rs -> mxw > ans) qry(p -> rs, a, b);
}
tree ** ins(tree *&p, point x, int d) {
if(p == null) {
p = newnode(x);
return &null;
}
tree **unb = ins(p -> son[p -> range.d[d] < x.d[d]], x, (d + 1) % K);
p -> up();
if(p -> unb()) unb = &p;
return unb;
}
int qry(point up) {
ans = 0;
qry(root, down, up);
return ans;
}
void ins(point p) {
tree ** unb = ins(root, p, 0);
if(*unb == null) return;
rebld(*unb);
}
};
KDT <K> kdt;
point a[maxn];
void solve() {
kdt.init();
int n;
cin >> n;
for(int i = 1 ; i <= n ; i ++) {
a[i].d[0] = i;
for(int j = 1; j < K ; j ++) {
cin >> a[i].d[j];
}
cin >> a[i].w;
}
flag = 1;
for(int i = 1; i <= n; i++) {
a[i].w += kdt.qry(a[i]);
cout << a[i].w << "\n";
kdt.ins(a[i]);
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
线段树维护区间矩阵乘法
//
// Created by blackbird on 2023/7/20.
//
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int mod = 1e9 + 7, N = 5e4 + 10;
string helowrd = "helowrd";
vector<int> pos[200];
int p[200];
int pow(int a, int b) {
int res = 1; a %= mod;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int inv(int x) {
return pow(x, mod - 2);
}
struct mat {
static const int n = 12;
int a[n][n];
int* operator[](int i) { return a[i]; }
void clear() {
memset(a, 0, sizeof a);
}
void identity() {
clear();
for (int i = 0; i < n; i++) {
a[i][i] = 1;
}
}
mat() { identity(); }
void init() {
identity();
for (auto ch : helowrd) {
for (auto i : pos[ch]) {
a[i][i - 1] = p[ch];
}
}
}
void set(int ch) {
identity();
for (auto i : pos[ch]) {
a[i][i - 1] = 1;
}
}
mat operator*(const mat & b) const {
mat res;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
unsigned long long sum = 0;
for (int k = 0; k < n; k += 4) {
sum = (sum + a[i][k] * b.a[k][j]);// % mod;
sum = (sum + a[i][k + 1] * b.a[k + 1][j]);// % mod;
sum = (sum + a[i][k + 2] * b.a[k + 2][j]);// % mod;
sum = (sum + a[i][k + 3] * b.a[k + 3][j]);// % mod;
}
res.a[i][j] = sum % mod;
}
}
return res;
}
};
mat tr[N << 2];
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
inline void pushup(int u) {
tr[u] = tr[rs] * tr[ls];//注意左乘
}
void build(int l, int r, int u) {
if (l == r) {
tr[u].init();
return;
}
build(l, mid, ls); build(mid + 1, r, rs);
pushup(u);
}
void upd(int pos, int val, int l, int r, int u) {
if (l == pos && r == pos) {
tr[u].set(val);
return;
}
if (pos <= mid) upd(pos, val, l, mid, ls);
else upd(pos, val, mid + 1, r, rs);
pushup(u);
}
mat ask(int s, int t, int l, int r, int u) {
if (s <= l && r <= t) return tr[u];
mat res;
if (s <= mid) res = res * ask(s, t, l, mid, ls);
if (t >= mid + 1) res = res * ask(s, t, mid + 1, r, rs);
return res;
}
inline void solve() {
int n;
cin >> n;
int sum = 0;
for (int i = 0; i < 7; i++) {
cin >> p[helowrd[i]];
sum += p[helowrd[i]];
}
for (int i = 0; i < 7; i++) {
p[helowrd[i]] = p[helowrd[i]] * inv(sum);
}
build(1, n, 1);
int q, op, x, y;
char str[2];
cin >> q;
while (q--) {
cin >> op;
if (op == 1) {
cin >> x >> str;
upd(x, str[0], 1, n, 1);
} else {
cin >> x >> y;
mat res = ask(x, y, 1, n, 1);
cout << res[10][0] << "\n";
}
}
}
signed main() {
//ios::sync_with_stdio(false); cin.tie(0); srand(time(0));
int T = 1; cin >> T;
pos['h'] = {1};
pos['e'] = {2};
pos['l'] = {3, 4, 9};
pos['o'] = {5, 7};
pos['w'] = {6};
pos['r'] = {8};
pos['d'] = {10};
while (T--) {
solve();
}
return 0;
}
/*
111
100
58260522 77914575 2436426 24979445 61648772 23690081 33933447
100
2 15 80
*/
广义二项式定理
使用普通生成函数即可得到该式子。场上考虑到多项式ln/exp等\(O(nlogn)\)算法过不了就没打。但是忘记简单的二项式(即便指数为负数)直接用广义二项式定理就好了。
//
// Created by blackbird on 2023/8/1.
//
#include <bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
using i64 = int64_t;
using u32 = uint32_t;
using u64 = uint64_t;
const int N = 4e6 + 10, P = 998244353;
struct mint {
int x;
constexpr mint(int x = 0) : x(x) {}
mint operator-() const { return x > 0 ? P - x : 0; }
mint operator+(mint o) const { return x + o.x < P ? x + o.x : x + o.x - P; }
mint operator-(mint o) const { return x - o.x < 0 ? x - o.x + P : x - o.x; }
mint operator*(mint o) const { return int(u64(x) * o.x % P); }
mint &operator+=(mint o) { return *this = *this + o; }
mint &operator-=(mint o) { return *this = *this - o; }
mint &operator*=(mint o) { return *this = *this * o; }
mint inv() const { return pow(P - 2); }
mint cbrt() const { return pow((P + P - 1) / 3); }
mint pow(auto k) const {
mint a = x;
mint b = 1;
for (; k; k >>= 1) {
if (k & 1)
b *= a;
a *= a;
}
return b;
}
mint sqrt() const {
if (pow(P >> 1).x != 1) return 0;
mint a = pow(60);
mint b = pow(119);
for (int k = 21; k >= 0; --k)
if (b.pow(1 << k).x != 1) {
a *= mint(3).pow(P >> (k + 2));
b *= mint(3).pow(P >> (k + 1));
}
return std::min(a.x, P - a.x);
}
friend ostream &operator<<(ostream &output, const mint &t) {
output << t.x;
return output;
}
friend istream &operator>>(istream &input, mint &t) {
input >> t.x;
return input;
}
};
mint f[N], invf[N];
void init() {
f[0] = 1; invf[0] = f[0].inv();
for (int i = 1; i < N; i++) {
f[i] = f[i - 1] * i;
invf[i] = f[i].inv();
}
}
mint a[N], b[N];
void solve() {
int n, m, k;
mint ans = 0;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
a[i].x = b[i].x = 0;
}
n -= m;
mint tmp = 1;
for (int i = 0; i * k <= n; ++i) {
a[i * k] = f[m] * invf[i] * invf[m - i];
if ((m - i) % 2 != 0) a[i * k] *= -1;
//tmp = -tmp * (m - i) * (((mint)(i + 1)).inv());
}
for (int i = 0; i <= n; ++i) {
b[i] = f[m + i - 1] * invf[i] * invf[m - 1];
if ((-m-i) % 2 != 0) b[i] *= -1;
}
for (int i = 0; i <= n; ++i) ans += a[i] * b[n - i];
ans *= invf[m] * f[n + m];
cout << ans << "\n";
}
signed main() {
//cin.tie(nullptr)->sync_with_stdio(false); srand(time(0));
init();
int T = 1; cin >> T;
while (T--) {
solve();
}
return 0;
}
线性基
//
// Created by blackbird on 2023/8/14.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
using namespace std;
const int N = 2e6 + 10;
vector<int> linear_base(vector<int> &v) {
vector<int> B;
for (auto x: v) {
for (auto b: B)
x = min(x, b ^ x);
for (auto &b: B)
b = min(b, b ^ x);
if (x)
B.push_back(x);
}
return B;
}
int n, k, a[N];
void solve() {
cin >> n >> k;
unordered_set<int> s;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s.insert(a[i]);
}
for (int i = 1; i <= n; i++) {
if (s.count(a[i] ^ k)) {
cout << "Alice\n";
return;
}
}
vector<int> v;
for (int i = 1; i <= n; i++) {
v.push_back(a[i] ^ k);
}
vector<int> b = linear_base(v);
if ((1ll << b.size()) == s.size()) cout << "Bob\n";
else
cout << "Draw\n";
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
srand(time(0));
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
牛客多效
吉司机线段树(区间历史最值)
题意
给⼀个长为 n 的序列,初始状态全为 0。
有两种操作,第⼀种将区间 [l, r] 内的数加 x ,
第⼆种对区间 [l, r] 内的每个数如果大于或等于 k 就减去 k( 在所有操作中相等)。
你需要执行 m 次操作,输出执行完所有操作后所有数⼀共被减了多少次。
n,m <= 1e6, x,k <= 1e9
题解
对于操作2,我们假装每次都减k,如果不该减,就记录一次借位,设某个数第 i 次操作后借位次数为bi,假装操作的最终值为ci。
结论:\(b_m = \lceil-\frac{\min c_i}{k}\rceil\)。从而第i个数的减少次数为第二次操作的总次数 - bm。
因而本题转化为转化为区间加、单点查询历史最小值。可以用吉司机线段树解决。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 1e18;
struct Node {
int l, r;
ll res, tag, tag2, res2;
// tag:当前标记;tag2:历史标记
// res:当前最小;res2:历史最小
};
class SegmentTree {
vector<Node> a;
void tag_init(int i) {
a[i].tag = a[i].tag2 = 0;
}
void tag_union(int fa, int i) {
if (a[fa].tag2 < 0) {
a[i].tag2 = min(a[i].tag2, a[i].tag + a[fa].tag2);
}
a[i].tag += a[fa].tag;
}
void tag_cal(int i) {
if (a[i].tag2 < 0) {
a[i].res2 = min(a[i].res2, a[i].res + a[i].tag2);
}
a[i].res += a[i].tag;
}
void pushdown(int i) {
tag_cal(i);
if (a[i].l != a[i].r) {
tag_union(i, i * 2);
tag_union(i, i * 2 + 1);
}
tag_init(i);
}
void pushup(int i) {
if (a[i].l == a[i].r) return;
pushdown(i * 2);
pushdown(i * 2 + 1);
a[i].res = min(a[i * 2].res, a[i * 2 + 1].res);
}
void build(int i, int l, int r) {
a[i].l = l, a[i].r = r;
tag_init(i);
if (l >= r) return;
int mid = (l + r) / 2;
build(i * 2, l, mid);
build(i * 2 + 1, mid + 1, r);
}
public:
// 区间+w
void update(int i, int l, int r, ll w) {
if (a[i].r < l || a[i].l > r || l > r) return;
pushdown(i);
if (a[i].l >= l && a[i].r <= r) {
a[i].tag += w;
a[i].tag2 = min(a[i].tag2, a[i].tag);
return;
}
update(i * 2, l, r, w);
update(i * 2 + 1, l, r, w);
pushup(i);
}
// 查询历史区间最小
ll query(int i, int l, int r) {
pushdown(i);
if (a[i].r < l || a[i].l > r || l > r) return inf;
if (a[i].l >= l && a[i].r <= r) {
return a[i].res2;
}
return min(query(i * 2, l, r), query(i * 2 + 1, l, r));
}
SegmentTree(int n) : a(n * 4) { build(1, 1, n); }
};
int main() {
ios::sync_with_stdio(false);
int n, m;
ll k, ans = 0;
cin >> n >> m >> k;
SegmentTree T(n);
while (m--) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) {
ll x;
cin >> x;
T.update(1, l, r, x);
} else {
// 预支
ans += r - l + 1;
T.update(1, l, r, -k);
}
}
for (int i = 1; i <= n; i++) {
ll g = T.query(1, i, i);
// 增补零散的那一部分
ans += g % k == 0 ? g / k : g / k - 1;
}
cout << ans << endl;
return 0;
}
可持久化线段树
UESTC_Bruteforce提出了一种更简单暴力的解法:
考虑可持久化数据结构的本质是保存历史版本,对整个区间进行操作并保存历史版本,则对一个子区间进行操作,必然对应一个子区间内状态正确的版本。
于是用另一棵可持久化线段树记录每个版本的操作,二分历史版本,在第一个满足区间操作次数等于给定次数的版本上求区间最值即可。
//
// Created by blackbird on 2023/8/4.
//
#include <iostream>
#include <queue>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 6e6 + 10;
int n, m, a[N], b[N], mxk = 0;
//ta:记录变换后序列,区间最值
//tb:记录修改次数,区间求和
struct hjt_tree {
int rt[N], idx = 0;
int mx[N], sum[N], lc[N], rc[N];
#define mid (l+r>>1)
void pushup(int u) {
mx[u] = max(mx[lc[u]], mx[rc[u]]);
sum[u] = sum[lc[u]] + sum[rc[u]];
}
void build(int l, int r, int &u, int a[]) {
if (!u) u = ++idx;
if (l == r) {
mx[u] = a[l];
return;
}
build(l, mid, lc[u], a);
build(mid + 1, r, rc[u], a);
pushup(u);
}
int copy(int u) {
int p = ++idx;
lc[p] = lc[u]; rc[p] = rc[u];
mx[p] = mx[u]; sum[p] = sum[u];
return p;
}
void modify(int l, int r, int pos, int x, int &u, int pre) {
u = copy(pre);
if (l == r) {
sum[u] = mx[u] = x;
return;
}
if (pos <= mid) modify(l, mid, pos, x, lc[u], lc[pre]);
else modify(mid + 1, r, pos, x, rc[u], rc[pre]);
pushup(u);
}
int query_sum(int s, int t, int l, int r, int u) {
if (s <= l && r <= t) return sum[u];
int res = 0;
if (s <= mid) res += query_sum(s, t, l, mid, lc[u]);
if (t >= mid + 1) res += query_sum(s, t, mid + 1, r, rc[u]);
return res;
}
int query_max(int s, int t, int l, int r, int u) {
if (s <= l && r <= t) return mx[u];
int res = 0;
if (s <= mid) res = max(res, query_max(s, t, l, mid, lc[u]));
if (t >= mid + 1) res = max(res, query_max(s, t, mid + 1, r, rc[u]));
return res;
}
#undef mid
} ta, tb;
bool operator<(const pii &a, const pii &b) {
if (a.first != b.first) return a.first > b.first;
return a.second < b.second;
}
void bruteforce() {
priority_queue<pii> q[2];
for (int i = 1; i <= n; i++) {
q[a[i] & 1].emplace(a[i], i);
}
ta.build(1, n, ta.rt[0], a);
tb.build(1, n, tb.rt[0], b);
while (!q[0].empty() || !q[1].empty()) {
mxk++;
pii t;
if (!q[0].empty()) t = q[0].top(), q[0].pop();
else t = q[1].top(), q[1].pop();
a[t.second] /= 2; b[t.second]++;
ta.modify(1, n, t.second, a[t.second], ta.rt[mxk], ta.rt[mxk - 1]);
tb.modify(1, n, t.second, b[t.second], tb.rt[mxk], tb.rt[mxk - 1]);
if (a[t.second] & 1) q[1].emplace(a[t.second], t.second);
else if (a[t.second] != 0) q[0].emplace(a[t.second], t.second);
//debug
// for (int i = 1; i <= n; i++) {
// cout << ta.query_max(i, i, 1, n, ta.rt[mxk]) << " ";
// } cout << "\n";
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
mxk = 0;
bruteforce();
while (m--) {
int s, t, k; cin >> s >> t >> k;
if (k == 0) {
cout << ta.query_max(s, t, 1, n, ta.rt[0]) << "\n";
continue;
}
int l = 1, r = mxk;
while (l < r) {
int mid = l + r >> 1;
if (tb.query_sum(s, t, 1, n, tb.rt[mid]) >= k) r = mid;
else l = mid + 1;
}
cout << ta.query_max(s, t, 1, n, ta.rt[l]) << "\n";
}
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false); srand(time(0));
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
拆位维护异或和
//
// Created by blackbird on 2023/7/31.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10, mod = 998244353;
int n, a[N];
int pre[N], pre_sum[N], pre0[33], pre1[33];
int suf[N], suf_sum[N], suf0[33], suf1[33];
int pre_ans[2][N];
void solve() {
for (int i = 1; i <= n; i++)
pre[i] = pre[i - 1] ^ a[i];
for (int i = 0; i <= n; i++) {
if (i != 0) pre_sum[i] = pre_sum[i - 1];
for (int j = 0; j <= 31; j++) {
if (pre[i] >> j & 1) {
pre1[j]++;
(pre_sum[i] += pre0[j] * (1ll << j)) %= mod;
}
else {
pre0[j]++;
(pre_sum[i] += pre1[j] * (1ll << j)) %= mod;
}
}
}
for (int i = n; i >= 1; i--)
suf[i] = suf[i + 1] ^ a[i];
for (int i = n + 1; i >= 1; i--) {
if (i != n + 1) suf_sum[i] = suf_sum[i + 1];
for (int j = 0; j <= 31; j++) {
if (suf[i] >> j & 1) {
suf1[j]++;
(suf_sum[i] += suf0[j] * (1ll << j)) %= mod;
}
else {
suf0[j]++;
(suf_sum[i] += suf1[j] * (1ll << j)) %= mod;
}
}
}
// n^2 solve:
// for (int l = 2; l <= n - 1; l++) {
// for (int r = l; r <= n - 1; r++) {
// ans = (ans + (pre[r] ^ pre[l - 1]) * pre_sum[l - 1] * suf_sum[r + 1]) % mod;
// }
// }
int ans = 0;
for (int i = 0; i <= 31; i++) {
pre0[i] = pre1[i] = 0;
}
for (int i = 0; i <= n; i++) {
int res = 0;
for (int j = 0; j <= 31; j++) {
if (pre[i] >> j & 1) {
pre1[j] = (pre1[j] + pre_sum[i]) % mod;
(res += pre0[j] * (1ll << j)) %= mod;
}
else {
pre0[j] = (pre0[j] + pre_sum[i]) % mod;
(res += pre1[j] * (1ll << j)) %= mod;
}
}
if (i >= 2 && i <= n - 1) {
ans = (ans + suf_sum[i + 1] * res) % mod;
}
}
cout << ans << "\n";
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
solve();
return 0;
}
//
// Created by blackbird on 2023/8/7.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10, mod = 998244353;
void solve() {
int n; cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a.begin() + 1, a.begin() + 1 + n);
int ans = 0;
for (int j = 0; j <= 30; j++) {
vector<int> premin = {0, 0}, spremin = {0, 0};
for (int i = 1; i <= n; i++) {
int x = a[i] >> j & 1;
vector<int> w = {0, 0}; w[x] = a[i];
//选取该右端点对答案的贡献:
// 只选i/选i以及前面异或为x^1的子集(此时new spremin = (s+1)premin)
int res = (w[1] + (spremin[x ^ 1] + premin[x ^ 1])) % mod;
ans = (ans + res * a[i] % mod * (1ll << j) % mod) % mod;
//不选i/只选i(此时x作为minx)/把i加入之前异或为0/1的子集(此时minx不变,s->s+1)
spremin = {(spremin[0] + w[0] + (spremin[x] + premin[x])) % mod, (spremin[1] + w[1] + (spremin[x ^ 1] + premin[x ^ 1])) % mod};
premin = {(premin[0] + w[0] + premin[x]) % mod, (premin[1] + w[1] + premin[x ^ 1]) % mod};
}
}
cout << ans << "\n";
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false); srand(time(0));
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
根号分治
概率卷积
竞赛图(有向完全图)
二分图博弈
支配树
李超线段树
HEOI2013 Segment(李超线段树模板)
要求在平面直角坐标系下维护两个操作:
- 在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)。
- 给定一个数 \(k\),询问与直线 \(x = k\) 相交的线段中,交点纵坐标最大的线段的编号。
输入的第一行是一个整数 \(n\),代表操作的个数。
接下来 \(n\) 行,每行若干个用空格隔开的整数,第 \(i + 1\) 行的第一个整数为 \(op\),代表第 \(i\) 次操作的类型。
若 \(op = 0\),则后跟一个整数 \(k\),代表本次操作为查询所所有与直线 \(x = (k + lastans - 1) \bmod 39989 + 1\) 相交的线段中,交点纵坐标最大的线段编号。
若 \(op = 1\),则后跟四个整数 \(x_0, y_0, x_1, y_1\),记 \(x_i' = (x_i + lastans - 1) \bmod 39989 + 1\),\(y_i' = (y_i + lastans - 1) \bmod 10^9 + 1\)。本次操作为插入一条两端点分别为 \((x_0', y_0')\),\((x_1',y_1')\) 的线段。
其中 \(lastans\) 为上次询问的答案,初始时,\(lastans = 0\)。
对于每次查询,输出一行一个整数,代表交点纵坐标最大的线段的编号。若不存在任何一条线段与查询直线有交,则输出 \(0\);若有多条线段与查询直线的交点纵坐标都是最大的,则输出编号最小的线段,同时 \(lastans\) 也应更新为编号最小的一条线段。
对于 \(30\%\) 的数据,保证 \(n \leq 10^3\)。
对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^5\),\(1 \leq k, x_0, x_1 \leq 39989\),\(1 \leq y_0, y_1 \leq 10^9\)。
不保证 \(x_0 \neq x_1\)。对于一条 \(x_0' = x_1'\) 的线段,认为其与 \(x = x_0'\) 的交点为其两端点中纵坐标较大的端点。
//
// Created by blackbird on 2023/7/18.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define pdi pair<double, int>
using namespace std;
const int N = 5e6 + 10, mod1 = 39989, mod2 = 1e9;
const double eps = 1e-9;
struct line {
double k, b;
} p[N];
int tr[N], cnt;
int cmp(double x, double y) {
if (x - y > eps) return 1;
if (y - x > eps) return -1;
return 0;
}
void add(int x0, int y0, int x1, int y1) {
cnt++;
if (x0 == x1) p[cnt] = {0, (double)max(y0, y1)};
else p[cnt].k = 1.0 * (y1 - y0) / (x1 - x0), p[cnt].b = y0 - p[cnt].k * x0;
}
double calc(int id, int x) {
return p[id].k * x + p[id].b;
}
void upd(int l, int r, int u, int id) {
int &pre = tr[u], mid = (l + r) >> 1;
if (cmp(calc(id, mid), calc(pre, mid)) == 1) swap(pre, id);
int bl = cmp(calc(id, l), calc(pre, l)), br = cmp(calc(id, r), calc(pre, r));
if (bl == 1 || (!br && id < pre)) upd(l, mid, u << 1, id);
if (br == 1 || (!br && id < pre)) upd(mid + 1, r, u << 1 | 1, id);
}
void update(int s, int t, int l, int r ,int u, int id) {
if (s <= l && r <= t) {
upd(l, r, u, id);
return;
}
int mid = (l + r) >> 1;
if (s <= mid) update(s, t, l, mid, u << 1, id);
if (t >= mid + 1) update(s, t, mid + 1, r, u << 1 | 1, id);
}
pdi pmax(pdi x, pdi y) {
int res = cmp(x.first, y.first);
if (res != 0) return res > 0 ? x : y;
return x.second < y.second ? x : y;
}
pdi query(int l, int r, int u, int pos) {
if (pos < l || pos > r) return {0, 0};
int mid = (l + r) >> 1;
pdi res = {calc(tr[u], pos), tr[u]};
if (l == r) return res;
return pmax(res, pmax(query(l, mid, u << 1, pos), query(mid + 1, r, u << 1 | 1, pos)));
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
int q, lastans = 0;
cin >> q;
while (q--) {
int op; cin >> op;
if (op == 0) {
int x; cin >> x;
x = (x + lastans - 1 + mod1) % mod1 + 1;
lastans = query(1, mod1, 1, x).second;
cout << lastans << "\n";
} else {
int x0, y0, x1, y1;
cin >> x0 >> y0 >> x1 >> y1;
x0 = (x0 + lastans - 1 + mod1) % mod1 + 1;
x1 = (x1 + lastans - 1 + mod1) % mod1 + 1;
y0 = (y0 + lastans - 1 + mod2) % mod2 + 1;
y1 = (y1 + lastans - 1 + mod2) % mod2 + 1;
if (x0 > x1) {
swap(x0, x1);
swap(y0, y1);
}
add(x0, y0, x1, y1);
update(x0, x1, 1, mod1, 1, cnt);
}
}
return 0;
}
Building Bridges(李超线段树优化序列dp)
题意
连接两根柱子 \(i,j\) 的代价是 \((h_i-h_j)^2+\sum\limits_{k=j+1}^{i-1}w_k\),连接具有传递性,求将 \(1,n\) 连接的最小代价。
题解
令 \(k = -2h_j\) , \(b = f_j-h_j^2-s_j\) , 查询 \(x = h_i\)处各条直线的最小值。
//
// Created by blackbird on 2023/7/19
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5e6 + 10, inf = 1e12;
struct line {
int k, b;
} l[N];
inline int calc(int id, int x) {
return l[id].k * x + l[id].b;
}
inline bool lt(int id1, int id2, int x) {
return calc(id1, x) < calc(id2, x);
}
struct LC_SegmentTree {
#define mid (l+r>>1)
#define ls (u<<1)
#define rs (u<<1|1)
int tr[N];
void add(int l, int r, int u, int id){
if (l == r) {
if (lt(id, tr[u], l)) tr[u] = id;
return;
}
if (lt(id, tr[u], mid)) swap(tr[u], id);
if (lt(id, tr[u], l)) add(l, mid, ls, id);
if (lt(id, tr[u], r)) add(mid + 1, r, rs, id);
}
int query(int l, int r, int u, int x){
int res = calc(tr[u], x);
if (l == r) return res;
if (x <= mid) res = min(res, query(l, mid, ls, x));
else res = min(res, query(mid + 1, r, rs, x));
return res;
}
} T;
int n, mx, h[N], s[N], f[N];
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> h[i];
mx = max(mx, h[i]);
}
for (int i = 1; i <= n; i++) {
cin >> s[i];
s[i] += s[i - 1];
}
l[0] = {0, inf};
l[1] = {-2 * h[1], f[1] + h[1] * h[1] - s[1]};
T.add(0, mx, 1, 1);
for (int i = 2; i <= n; i++) {
f[i] = T.query(0, mx, 1, h[i]) + h[i] * h[i] + s[i - 1];
l[i] = {-2 * h[i], f[i] + h[i] * h[i] - s[i]};
T.add(0, mx, 1, i);
}
cout << f[n] << "\n";
return 0;
}
Escape The Maze(李超线段树+线段树合并优化树上dp)
题意
一颗树,边有边权,可能为负。从根节点开始,每次可以走向子树中任意一点,直到到达叶结点。每次的花费为\((s-L)^2\), \(s\) 为路程,\(L\) 为定值,求最小花费。
题解
//
// Created by blackbird on 2023/7/21.
//
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10, inf = 1e10;
int n, L;
vector<pii> g[N];
int f[N], sum[N], root[N], lson[N], rson[N], idL[N], cnt;
struct Line {
int k, b;
} line[N];
int calc(int id, int x) {
return line[id].k * x + line[id].b;
}
#define mid (l+r>>1)
void insert(int &u, int l, int r, int id) {
if (!u) {
u = ++cnt;
idL[u] = id;
return;
}
if (calc(idL[u] , mid) > calc(id , mid)) {
swap(idL[u] , id);
}
if (calc(idL[u] , l) <= calc(id , l) && calc(idL[u] , r) <= calc(id , r)) {
return ;
}
if (calc(idL[u] , l) > calc(id , l)) {
insert(lson[u] , l , mid , id);
} else {
insert(rson[u] , mid + 1 , r , id);
}
}
int merge(int u, int v, int l, int r) {
if (!u || !v) {
return u + v;
}
insert(u , l , r , idL[v]);
lson[u] = merge(lson[u] , lson[v] , l , mid);
rson[u] = merge(rson[u] , rson[v] , mid + 1 , r);
return u;
}
int query(int u, int l, int r, int x) {
if (!u) {
return inf;
}
int res = calc(idL[u], x);
if (x <= mid) {
res = min(res, query(lson[u], l, mid, x));
} else {
res = min(res, query(rson[u], mid + 1, r, x));
}
return res;
}
void dfs(int u, int fa) {
int son = 0;
for (auto [v, w] : g[u]) {
if (v == fa) continue;
sum[v] = sum[u] + w;
dfs(v, u);
root[u] = merge(root[u], root[v], -inf, inf);
son++;
}
if (!son) {
f[u] = 0;
} else {
f[u] = query(root[u], -inf, inf, sum[u]) + sum[u] * sum[u] + L * L;
}
line[u] = {-2 * sum[u] + 2 * L, sum[u] * sum[u] - 2 * sum[u] * L + f[u]};
insert(root[u], -inf, inf, u);
}
void solve() {
cin >> n >> L;
for (int i = 1; i <= n; i++) {
g[i].clear();
}
for (int i = 1; i <= n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].emplace_back(v, w);
g[v].emplace_back(u, w);
}
int q; cin >> q;
while (q--) {
int u; cin >> u;
sum[u] = 0;
for (int i = 1; i <= n; i++) root[i] = 0;
for (int i = 1; i <= cnt; i++) {
lson[i] = rson[i] = idL[i] = 0;
}
cnt = 0;
dfs(u, 0);
cout << f[u] << "\n";
}
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); srand(time(0));
int T = 1; cin >> T;
while (T--) {
solve();
}
return 0;
}
Traffic(tarjan+李超线段树)
8月毒瘤场/其他比赛
平面分治/双指针枚举半平面
从前,阿尔戈兰和伯兰是一个国家,但那个时代已经一去不复返了。现在,它们是两个不同的国家,但它们的城市分散在共同的领土上。
所有城市都表示为笛卡尔平面上的点。阿尔戈兰由\(a\)个城市组成,编号从\(1\)到\(a\)。第\(i\)个阿尔戈兰城市的坐标是一对整数\((xa_i, ya_i)\)。同样,伯兰也由从\(1\)到\(b\)的\(b\)个城市组成。第\(j\)-个伯兰城市的坐标是一对整数\((xb_j, yb_j)\)。上述\(a+b\)个城市中没有三个位于一条直线上。
作为联合各国的第一步,伯兰决定修建多条双向高速公路。每条高速公路都是一条线段,起点是伯兰的一个城市,终点是阿尔戈兰的一个城市。除了高速公路的起点和终点,高速公路之间不能有任何交点。此外,高速公路必须连接所有\(a+b\)个城市。这里的连通性指的是,人们可以通过高速公路从指定的 \(a+b\)个城市中的任何一个到达 \(a+b\)个城市中的任何一个。请注意,所有高速公路都是双向的,这意味着人们可以在每条高速公路上双向行驶。
每个\(b\)城市的市长伯兰市的市长都会拨出一笔预算来修建从该市出发的高速公路。因此,你会得到数字\(r_1, r_2, \dots, r_b\),其中\(r_j\)是将从第\(j\)个伯兰市开始的高速公路的数量。分配的总预算非常紧张,仅能满足修建最低限度的必要高速公路的需要。换句话说,\(r_1+r_2+\dots+r_b=a+b-1\)
帮助伯兰市修建高速公路,以便
- 每条高速公路都是一条连接伯兰市和阿尔戈兰市的线段、
- 除了高速公路的起点或终点,高速公路之间没有交叉、
- 高速公路连接所有\(a+b\)个城市(所有高速公路都是双向的)、
- 有\(r_j\)条高速公路的起点位于第\(j\)个伯兰市。
题解:
考虑分治,每次找度数最大的点,枚举一条经过它的直线,将这个点和整个平面分成两部分分治求解,使得该点可以满足其度数要求,直到存在一类点只有一个的平凡情况。
可以证明,我们在任何情况下总能找到该分界平面。注意一下双指针的细节。
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
#define pii pair<int, int>
using namespace std;
using point_t = long long;//全局数据类型,可修改为 long long 等
constexpr point_t eps = 1e-8;
constexpr long double PI = 3.1415926535897932384l;
// 点与向量
template<typename T>
struct point {
T x, y;
int r, id;
bool operator==(const point &a) const { return (abs(x - a.x) <= eps && abs(y - a.y) <= eps); }
bool operator!=(const point &a) const { return !(*this == a); };
point operator+(const point &a) const { return {x + a.x, y + a.y}; }
point operator-(const point &a) const { return {x - a.x, y - a.y}; }
T operator^(const point &a) const { return x * a.y - y * a.x; }// 叉积,注意优先级
};
using Point = point<point_t>;
// 极角排序
struct argcmp {
bool operator()(const Point &a, const Point &b) const {
const auto quad = [](const Point &a) {
if (a.y < -eps) return 1;
if (a.y > eps) return 4;
if (a.x < -eps) return 5;
if (a.x > eps) return 3;
return 2;
};
const int qa = quad(a), qb = quad(b);
if (qa != qb) return qa < qb;
const auto t = a ^ b;
// if (abs(t)<=eps) return a*a<b*b-eps; // 不同长度的向量需要分开
return t > eps;
}
};
int cnt = 0;
void work(vector<Point> &pa, vector<Point> &pb) {
if (pa.size() == 1) {
for (auto p: pb) {
cout << p.id << " " << pa[0].id << "\n";
cnt++;
}
return;
}
if (pb.size() == 1) {
for (auto p: pa) {
cout << pb[0].id << " " << p.id << "\n";
cnt++;
}
return;
}
int mxr = 0;
Point o;
for (auto t: pb) {
if (t.r > mxr) {
mxr = t.r;
o = t;
}
}
vector<Point> v;
for (auto t: pa) {
t.x -= o.x, t.y -= o.y;
v.push_back(t);
}
for (auto t: pb) {
if (t != o) {
t.x -= o.x, t.y -= o.y;
v.push_back(t);
}
}
o.x = o.y = 0;
sort(v.begin(), v.end(), argcmp());
int sum = 1 - v[0].r;
bool ok = 0;
for (int i = 0, j = 1; i < v.size(); i++) {
// if (i != 0) {
// sum -= 1 - v[i - 1].r;
// }
if (j == i) {
sum += 1 - v[j].r;
j = (j + 1) % v.size();
}
while ((v[i] ^ v[j]) >= 0 && j != i) {
sum += 1 - v[j].r;
j = (j + 1) % v.size();
}
if (sum >= 1 && sum <= mxr - 1) {//sum + (1 - t.r) = 1;
Point t1 = o, t2 = o;
t1.r = sum, t2.r = mxr - sum;
vector<Point> v1, v2;
v2.push_back(t1);
for (int k = i; k != j; k = (k + 1) % v.size()) {
v[k].r ? v2.push_back(v[k]) : v1.push_back(v[k]);
}
work(v1, v2);
v1.clear();
v2.clear();
v2.push_back(t2);
for (int k = j; k != i; k = (k + 1) % v.size()) {
v[k].r ? v2.push_back(v[k]) : v1.push_back(v[k]);
}
work(v1, v2);
ok = 1;
break;
}
sum -= 1 - v[i].r;
}
assert(ok);
}
void solve() {
int n, m;
cin >> n >> m;
vector<Point> pa(n), pb(m);
for (int i = 0; i < m; i++) {
cin >> pb[i].r;
}
for (int i = 0; i < n; i++) {
cin >> pa[i].x >> pa[i].y;
pa[i].r = 0;
pa[i].id = i + 1;
}
for (int i = 0; i < m; i++) {
cin >> pb[i].x >> pb[i].y;
pb[i].id = i + 1;
}
cout << "YES\n";
cnt = 0;
work(pa, pb);
//assert(cnt == n + m - 1);
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
Z算法/双哈希
虽然但是,使用双哈希也是时间复杂度为\(O(n)\)且保证正确性的做法,只是常数较大。
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e6 + 10;
const int M = 2e9;
const int mod[2] = {998244853, 1000000009};
const int P[2] = {29, 13331};
ULL b[2][N];
struct Hash_Pair{
long long h[2][N];
inline void Get_Hash(const char *s,int n){
for(int i = 0; i< 2; i++)
for(int j = 1; j <= n; j++){
h[i][j] = (h[i][j-1] * P[i] % mod[i] + s[j]) % mod[i];
}
}
inline pair<long long, long long> get(int l,int r){
return {(h[0][r] - h[0][l-1] * b[0][r-l+1] % mod[0] + mod[0]) % mod[0],
(h[1][r] - h[1][l-1] * b[1][r-l+1] % mod[1] + mod[1]) % mod[1]};
}
} hs, ht1, ht2;
inline pii add(pii h1, int n1, pii h2, int n2) {
pii res;
res.first = (h1.first * b[0][n2] % mod[0] + h2.first) % mod[0];
res.second = (h1.second * b[1][n2] % mod[1] + h2.second) % mod[1];
return res;
}
char s[N], t1[N], t2[N];
inline int pti(pii p) {
return p.first * M + p.second;
}
void solve() {
scanf("%s%s%s", t1 + 1, t2 + 1, s + 1);
int n = strlen(s + 1), m1 = strlen(t1 + 1), m2 = strlen(t2 + 1);
hs.Get_Hash(s, n);
ht1.Get_Hash(t1, m1);
ht2.Get_Hash(t2, m2);
unordered_map<int,int> cnt;
for (int i = 1; i <= m1 + 1; i++) {
pii res = add(ht1.get(1, i - 1), i - 1, ht2.get(1, m2), m2);
res = add(res, i - 1 + m2, ht1.get(i, m1), m1 - i + 1);
cnt[pti(res)]++;
}
int ans = 0;
for (int i = 1; i <= n; i++) {
if (i + m1 + m2 - 1 > n) break;
int res = pti(hs.get(i, i + m1 + m2 - 1));
if (cnt.count(res)) ans += cnt[res];
}
printf("%lld\n", ans);
}
signed main() {
//cin.tie(nullptr)->sync_with_stdio(false); srand(time(0));
b[0][0] = b[1][0] = 1;
for(int i = 0; i < 2; i++)
for(int j = 1; j <= N - 1; j++)
b[i][j] = b[i][j-1] * P[i] % mod[i];
int T = 1; scanf("%lld", &T);
while (T--) {
solve();
}
return 0;
}
7月
20230710
I - Visiting Friend(点双连通分量/圆方树)
题意
多次询问两个点之间所有路径可能经过的点数,路径只需要满足起点和终点不重复经过。
\(N,M,Q ≤ 5\times10^5\)
题解
考虑点双连通分量中任意两点都存在两条不相交的路径的性质,将点双缩点建树,则树上路径对应的点都是满足条件的。
点双缩点的处理较为复杂,不如用圆方树解决,方点点权设为0,圆点点权设为1。维护一下子树和,讨论两个点的LCA是不是其中一个点两种情况,删去不可能经过的点即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int n, tot, m;
vector<int> g[N], G[N], dcc[N];
int w[N];
int dfn[N], low[N], idx;
stack<int> s;
void yfs(int u, int fa) {
dfn[u] = low[u] = ++idx;
s.push(u);
int cnt = 0;
for (auto v : g[u]) {
if (v == fa) continue;
if (!dfn[v]) {
yfs(v, u);
low[u] = min(low[u], low[v]);
if (dfn[u] <= low[v]) {
tot++;
int x;
do {
x = s.top(); s.pop();
G[x].push_back(tot); G[tot].push_back(x);
} while (x != v);
G[u].push_back(tot); G[tot].push_back(u);
w[tot] = 0;
}
} else low[u] = min(low[u], dfn[v]);
}
}
int dep[N];
int sum[N];
int f[N][22];
void dfs(int u, int fa) {
//cout << u << "\n";
f[u][0] = fa;
dep[u] = dep[fa] + 1;
sum[u] = w[u];
for (int i = 1; i <= 21; i++) {
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (auto v : G[u]) {
if (v == fa) continue;
dfs(v, u);
sum[u] += sum[v];
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int k = dep[x] - dep[y];
for (int i = 0; i <= 21; i++) {
if (k >> i & 1) x = f[x][i];
}
if (x == y) return x;
for (int i = 21; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
void solve() {
cin >> n >> m;
tot = n;
idx = 0;
while (!s.empty()) s.pop();
for (int i = 1; i <= n * 4; i++) {
dcc[i].clear(); g[i].clear(); G[i].clear();
dfn[i] = low[i] = dep[i] = sum[i] = 0;
w[i] = 1;
for (int j = 0; j <= 21; j++) f[i][j] = 0;
}
for (int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v); g[v].push_back(u);
}
yfs(1, 0);
dfs(1, 0);
int q; cin >> q;
while (q--) {
int u, v; cin >> u >> v;
if (dep[u] > dep[v]) swap(u, v);
int t = lca(u, v);
if (t == u) {
int k = dep[v] - dep[u] - 1;
int tv = v;
for (int i = 0; i <= 21; i++) {
if (k >> i & 1) tv = f[tv][i];
}
cout << sum[tv] - sum[v] + 2 << "\n";
} else {
cout << n - (sum[u] - 1) - (sum[v] - 1) << "\n";
}
}
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
int T = 1; cin >> T;
while (T--) {
solve();
}
return 0;
}
/*
1
9 9
1 2 2 3 3 4 4 5 5 6 6 7 7 1
5 8 5 9
9999
8 9
*/
E - Sum Over Zero(树状数组优化dp)
题意
给定一个序列,选择一些不相交子区间,满足每个子区间的和非负,最大化区间的总长度。
题解
设 \(dp_i\) 表示以 \([1,i]\) 的答案,很容易想到 \(O(n^2)\) 的dp:
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1]; //初始化:不选i
for (int j = 0; j < i; j++)
if (sum[i] - sum[j] >= 0) //更新:枚举所有[j+1,i]非负的区间
dp[i] = max(dp[i], dp[j] + (i - j) )
}
考虑优化:移个项,则更新时有
只需要使用树状数组(值域线段树)将每次的dp[i] - i
插入到下标sum[i]
处,维护前缀最大值即可。
sum[i]
可能很大,需要离散化 。同时注意一开始dp[0]-0=0,sum[0]=0
。需要将离散化后的0插入到树状数组中。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e5 + 10, inf = 1e17;
int n, a[N], sum[N], dp[N];
int pos0, val[N];
void work(int arr[]) {
for (int i = 1; i <= n; i++) val[i] = arr[i];
sort(val + 1, val + 1 + n);
int m = unique(val + 1, val + 1 + n) - val - 1;
for (int i = 1; i <= n; i++) {
arr[i] = lower_bound(val + 1, val + 1 + m, arr[i]) - val;
}
pos0 = lower_bound(val + 1, val + 1 + m, 0) - val;
}
int t[N];
void add(int i, int x) {
for (; i <= n; i += i & -i) {
t[i] = max(t[i], x);
}
}
int ask(int i) {
int res = -inf;
for (; i; i -= i & -i) {
res = max(res, t[i]);
}
return res;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
t[i] = -inf;
}
work(sum);
add(pos0, 0);
for (int i = 1; i <= n; i++) {
dp[i] = max(dp[i - 1], ask(sum[i]) + i);
add(sum[i], dp[i] - i);
}
cout << dp[n] << "\n";
}
signed main() {
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
20230711
C - Necklace(最小表示法)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 10;
char s[N];
int main() {
scanf("%s", s);
int n = strlen(s);
int p1 = 0, p2 = 1, i = 0;
while (p1 < n && p2 < n && i < n) {
if (s[(p1 + i) % n] == s[(p2 + i) % n]) {
i++;
} else {
if (s[(p1 + i) % n] > s[(p2 + i) % n]) {
p1 += i + 1;
} else {
p2 += i + 1;
}
i = 0;
if (p1 == p2) p2++;
}
}
for (int i = 0; i < n; i++) putchar(s[(p1 + i) % n]);
return 0;
}
TODO:lyndon分解
B - 我承认阁下的字符串匹配很强(kmp+dp)
题意
给定一个字符串S , 包含小写字母和问号,其中每个问号都可以替换成任意一个小写字母,你需要构造一个方案,最大化字符串T在S出现的次数。
题解
//
// Created by blackbird on 2023/7/9.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10;
int n, m, ne[N], ans;
char s[N], t[N];
void getnext(char t[], int m) {
for (int i = 2, j = 0; i <= m; i++) {
while (j && t[j + 1] != t[i]) j = ne[j];
if (t[j + 1] == t[i]) j++;
ne[i] = j;
}
}
int f[N], g[N];
void solve(char s[], int n, char t[], int m) {
for (int i = 1; i - 1 + m <= n; i++) {
bool ok = 1;
for (int j = 1; j <= m; j++) {
if (s[i - 1 + j] != t[j] && s[i - 1 + j] != '?') {
ok = 0; break;
}
}
if (ok) { //T在相同前后缀处重叠
int j = ne[m];
while (j) {
g[i - 1 + m] = max(g[i - 1 + m], g[i - 1 + j] + 1);
j = ne[j];
}
g[i - 1 + m] = max(g[i - 1 + m], f[i - 1] + 1);
}
f[i - 1 + m] = max(f[i - 1 + m - 1], g[i - 1 + m]);
}
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> s + 1 >> t + 1;
n = strlen(s + 1); m = strlen(t + 1);
getnext(t, m);
solve(s, n, t, m);
cout << f[n] << "\n";
return 0;
}
K - Difficult Constructive Problem(上下界贪心)
题意
给定一个长度为 n 的字符串 s ,其中 si ∈ {‘0’, ‘1’, ‘?’}
另外给定一个整数 k,
将字符串中 ‘?’ 换成 ‘0’ 或 ‘1’,使得满足 1 ≤ i < n 且 si != si+1 的下标 i 恰有 k 个。
求字典序最小的一组解。
题解
贪心构造,通过维护当前选择的结果上下界,进行局部最优选择。
//
// Created by blackbird on 2023/7/11.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10, inf = 1e9;
int n, k, a[N];
char s[N];
int mn[N][2], mx[N][2];
string calc() {
for (int i = 0; i <= 1; i++) {
if (s[n] == i + '0') mn[n][i] = mx[n][i] = 0;
else mn[n][i] = inf, mx[n][i] = -inf;
}
for (int i = n - 1; i > 0; i--) {
for (int j = 0; j <= 1; j++) {
if (s[i] == j + '0' || s[i] == '?') {
mn[i][j] = min(mn[i + 1][j], mn[i + 1][1 - j] + 1);
mx[i][j] = max(mx[i + 1][j], mx[i + 1][1 - j] + 1);
} else mn[i][j] = inf, mx[i][j] = -inf;
}
}
if (k % 2 != mn[1][s[1] - '0'] % 2) {
//cout << "wrong evenodd\n";
return "2";
}
string res;
int cnt = 0;
for (int i = 1; i <= n; i++) {
bool ok = 0;
for (int j = 0; j <= 1; j++) {
int t = (i > 1 && j != res[i - 2] - '0' ? 1 : 0);
if (mn[i][j] <= k - (cnt + t) && k - (cnt + t) <= mx[i][j]) {
res.push_back(j + '0');
cnt += t;
ok = 1; break;
}
}
if (!ok) {
return "2";
}
}
return res;
}
void solve() {
cin >> n >> k >> s + 1;
char s1 = s[1], sn = s[n];
string ans = "2";
for (char i = '0'; i <= '1'; i++) if (s1 == '?' || s1 == i) {
for (char j = '0'; j <= '1'; j++) if (sn == '?' || sn == j) {
s[1] = i;
s[n] = j;
ans = min(ans, calc());
}
}
if (ans == "2") cout << "Impossible\n";
else cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
int T = 1; cin >> T;
while (T--) {
solve();
}
return 0;
}
20230712
M - Window Arrangement(费用流)
题意:
KAIST 的预算快用完了--他们需要一些钱!他们认为,与 KAIST 的其他建筑相比,宿舍楼过于豪华;他们计划卖掉所有宿舍楼,新建一栋完全不美观的宿舍楼。
新的宿舍楼将是方格子形状的--没有比这更无聊的了--大小为 \(N\times M\),每个格子是一个房间,供学生居住。我们将增加一些窗户,因为我们希望学生们白天能晒到太阳!
我们计划在\((i, j)\)号房间正好安装\(w_{i, j}\)扇窗户。每个房间的四周都有\(4\)个单元的网格边。一扇窗户可以建在网格单元边的一侧,一个单元边的每一侧最多可以建一扇窗户--因此每个单元都有\(0\)到\(4\)扇窗户。窗户是单面的:在单元边的另一侧的窗户不算房间里的窗户。
不幸的是,当自己的私人空间被别人透过窗户观看时,学生会感到非常不舒服。总的不舒服就是有多少对学生\(\{a, b\}\),使得\(a\)和\(b\)可以通过窗户看到对方的私人空间。
换句话说,如果一个单元边的两侧都有窗户,那么总的不适感就会增加,增加的幅度是共用窗户的两个房间的居住人数的乘积。
给定房间 \((i, j)\)计划安装窗户的数量为 \(w_{i, j}\),房间 \((i, j)\)的居住人数为 \(p_{i, j}\)。您的任务是找出通过合理安排窗户可以达到的最小总不适度。
题解:
显然可以用费用流解决,注意选取恰当的建图方式。
#pragma GCC optimize(3)
#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1e5 + 10;
struct Edge{
int from, to, cap, flow, cost;
Edge(int u,int v,int c,int f,int w):from(u),to(v),cap(c),flow(f),cost(w){}
};
struct MCMF{
int n, m, inq[N], d[N], p[N], a[N];
vector<Edge> edges;
vector<int> G[N];
void init(int n){
this->n = n;
for(int i = 0; i <= n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int cap,long long cost){
edges.push_back(Edge(from,to,cap,0,cost));
edges.push_back(Edge(to,from,0,0,-cost));
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BellmanFord(int s,int t,int &flow,long long &cost){
for(int i = 0; i <= n; i++) d[i] = INF;
memset(inq, 0, sizeof inq);
d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF;
queue<int> Q;
Q.push(s);
while(!Q.empty()){
int u = Q.front(); Q.pop();
inq[u] = 0;
for(int i = 0; i < G[u].size(); i++){
Edge& e = edges[G[u][i]];
if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
d[e.to] = d[u]+e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u],e.cap-e.flow);
if(!inq[e.to]) {Q.push(e.to); inq[e.to] = 1;}
}
}
}
if(d[t]==INF) return false;
flow += a[t];
cost += (long long)d[t]*(long long)a[t];
for(int u = t; u!=s; u = edges[p[u]].from){
edges[p[u]].flow+=a[t];
edges[p[u]^1].flow-=a[t];
}
return true;
}
int MincostMaxflow(int s,int t,long long &cost){
int flow = 0;
cost = 0;
while(BellmanFord(s,t,flow,cost));
return flow;
}
} g;
int n, m, O, S, T, a[51][51], w[51][51];
int calc_grid(int x, int y) {
return (x - 1) * m + y;
}
int calc_window(int x, int y, int dx, int dy) {
// ‘|’ windows: n*m + 1 ~ n*m + n*(m-1) = n*(2m-1)
// '-' windows: n*(2m-1) + 1 ~ n*(2m-1) + (n-1)*m
if (dx == 0) {
int base = n * m;
if (dy == -1) y--, dy = 1; //left to right
int delta = (m - 1) * (x - 1) + y;
return base + delta;
} else {
int base = n * (m * 2 - 1);
if (dx == -1) x--, dx = 1;//up to down
int delta = m * (x - 1) + y;
return base + delta;
}
}
signed main() {
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> a[i][j];
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> w[i][j];
g.init(3 * n * m + 5);
O = 3 * n * m;
S = 3 * n * m + 1;
T = 3 * n * m + 2;
//cout << "?????OST\n";
g.AddEdge(O, T, 1e9, 0);
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, -1, 1};
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
g.AddEdge(S, calc_grid(i, j), w[i][j], 0);
for(int d = 0; d < 4; d++) {
int x = i + dx[d], y = j + dy[d];
if(x < 1 || y < 1 || x > n || y > m) {
g.AddEdge(calc_grid(i, j), O, 1, 0);
continue;
}
g.AddEdge(calc_grid(i, j), calc_window(i, j, dx[d], dy[d]), 1, 0);
if (dx[d] + dy[d] == 1) {
g.AddEdge(calc_window(i, j, dx[d], dy[d]), T, 1, 0);
g.AddEdge(calc_window(i, j, dx[d], dy[d]), T, 1, a[i][j] * a[x][y]);
}
}
}
}
long long cost = 0;
g.MincostMaxflow(S, T, cost);
cout << cost << "\n";
return 0;
}
Squirrel Game(阶梯nim博弈)
考虑模k意义下,构成k个倒着的阶梯Nim问题。
有n个位置1...n,每个位置上有ai个石子。有两个人轮流操作。
操作步骤是:挑选1...n中任一一个存在石子的位置i,将至少1个石子移动至i−1位置(也就是最后所有石子都堆在在0这个位置)。谁不能操作谁输。
求先手必胜还是必败。
阶梯nim问题的结论为,奇数位置的异或和决定状态。(0为先手必败)。
//by Bob_Wang
#include<cstdio>
#include<algorithm>
#define ll long long
#define maxn 100005
using namespace std;
int m,n,k;
ll ans,a[maxn],del[maxn];
int main()
{
scanf("%d%d%d",&m,&n,&k);
for(int i=1;i<=n;++i)
{
scanf("%lld",&a[i]);
a[i]-=i;
del[i]=a[i]-a[i-1];
}
// for(int i=1;i<=min(n,k);++i)
// {
// ll ret=0,mx=0;
// for(int j=i;j<=n;j+=k)
// {
// ret+=del[j];
// mx=max(mx,del[j]);
// }
// if(mx<ret)
// ret++;
// ans^=ret;
// }
for(int i=n;i>max(0,n-k);--i)
{
ll ret=0;
for(int j=i,fl=1;j>=1;j-=k)
{
if(fl)
ret^=del[j];
fl^=1;
}
ans^=ret;
}
if(ans)
printf("Twinkle\n");
else printf("Nova\n");
return 0;
}
20230713
Edge Weight Assignment(构造)
题意
给定一颗无根树,你需要给所有边加上一个正整数权值,满足任意一对叶子节点的路径上边权值的异或为0,问你最少和最多需要加几种权值。
题解
首先考虑一个结论:我们选定一个度数不为1的结点为根,那么任意一对叶子结点的路径异或和为0,等价于任意叶子结点到根节点的路径异或和相等。
最少:
考虑所有叶子结点的深度。如果它们的奇偶性都相同,显然令所有边权都为1即可,答案为1。
否则,只用1或2种权值都无法满足要求。(因为存在叶子结点对的路径长度为奇数,不可能用奇数个1种或2种数使得异或和为0)。
可以这样构造
- 所有深度为奇数的叶子节点,连向父节点的边权设为01
- 所有深度为偶数的叶子节点,连向父节点的边权设为10
- 其他边的边权为1
这样,所有叶子结点到根节点的路径异或和都为10,任意一对叶子结点路径异或和为0,答案为3。
最多:
由于边权可以无限大,我们可以尝试把每条边都分配不同的二进制位为1,最后用连接根节点的边补充,保证每个叶子结点到根的路径异或和相等即可。
唯一的限制是,如果多个叶子结点具有相同的父节点,则它们连向父亲的边权必须相同。统计这种情况的数量即可。
//
// Created by blackbird on 2023/7/13.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10;
int n, rt, dep[N], deg[N], cnt[2], mn, mx;
vector<int> g[N];
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
if (deg[u] == 1) cnt[dep[u] % 2]++;
int cnt_leaf = 0;
for (auto v : g[u]) {
if (v == fa) continue;
if (deg[v] == 1) cnt_leaf++;
dfs(v, u);
}
if (cnt_leaf > 1) mx -= cnt_leaf - 1;
}
void solve() {
cin >> n;
for (int i = 1; i <= n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v); g[v].push_back(u);
deg[u]++; deg[v]++;
}
for (int i = 1; i <= n; i++) {
if (deg[i] > 1) {
rt = i;
break;
}
}
mx = n - 1;
dfs(rt, 0);
mn = cnt[0] && cnt[1] ? 3 : 1;
cout << mn << " " << mx << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); srand(time(0));
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
Imbalance Value of a Tree(点分治并查集)
给你一棵由n个顶点组成的树T。每个顶点上都写有一个数字;写在顶点 i上的数字是 a**i。让我们把函数I(x, y)表示为连接顶点x和y的简单路径上a**i的最大值和最小值之差。
计算
题解:将点权推到边权上,并查集维护即可。
//
// Created by blackbird on 2023/7/13.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10;
int n, val[N];
struct edge {
int u, v, w;
bool operator<(const edge& x) const {
return w < x.w;
}
} e1[N], e2[N];
int f[N], siz[N];
int find(int x) {
return x == f[x] ? f[x] : find(f[x]); //维护siz,不用路径压缩
}
int unite(int x, int y) {
if (siz[x] > siz[y]) swap(x, y);
f[x] = y;
siz[y] += siz[x];
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> val[i];
}
for (int i = 1; i <= n - 1; i++) {
int u, v; cin >> u >> v;
e1[i] = {u, v, max(val[u], val[v])};
e2[i] = {u, v, min(val[u], val[v])};
}
int ans = 0;
//calc max
sort(e1 + 1, e1 + n);
for (int i = 1; i <= n; i++) {
f[i] = i; siz[i] = 1;
}
for (int i = 1; i <= n - 1; i++) {
int u = find(e1[i].u), v = find(e1[i].v), w = e1[i].w;
ans += siz[u] * siz[v] * w;
unite(u, v);
}
//calc min
sort(e2 + 1, e2 + n);
for (int i = 1; i <= n; i++) {
f[i] = i; siz[i] = 1;
}
for (int i = n; i >= 1; i--) {
int u = find(e2[i].u), v = find(e2[i].v), w = e2[i].w;
ans -= siz[u] * siz[v] * w;
unite(u, v);
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); srand(time(0));
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
Hyper String(神奇的DP)
给定\(N(N \le 2000)\)个小写英文字母组成的字符串\({a_i}\),总长度不超过\(10 ^ 6\)。再给定一个长度为\(M(M \le 2000)\)的正整数序列\({b_i}(1 \le b_i \le N)\),定义\(A = a_{b_1} + a_{b_2} + ... + a_{b_M}\),其中\(+\)表示字符串的连接。最后给定一个小写英文字母组成的字符串\(B(|B| \le 2000)\),求\(A\)和\(B\)的最长公共子序列长度。
20230714
M - Computational Geometry(计算几何/区间DP)
题意
将一个凸多边形分割为两个凸多边形,最小化两个凸多边形的直径平方和。
题解
设f[i][j]
表示第 \(i\) 个顶点到第 \(j\) 个顶点之间凸包的直径。
则有区间dp
f[i][j] = max({f[(i + 1) % n][j], f[i][(j - 1 + n) % n], dis2(i, j)});
枚举所有点对,计算即可。
ans = min(ans, f[i][j] + f[j][i])
//
// Created by zjun on 2023/7/14.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e3 + 10, inf = 7e18;
int n, m, f[N][N];
struct point {
int x, y;
point operator+(const point &a) const { return {x + a.x, y + a.y}; }
point operator-(const point &a) const { return {x - a.x, y - a.y}; }
point operator*(const int &a) const { return {a * x, a * y}; }
int operator*(const point &a) const { return x * a.x + y * a.y; }
int operator^(const point &a) const { return x * a.y - y * a.x; }
int len2() const { return (*this) * (*this); }
int dis2(const point &a) const { return (a-(*this)).len2(); }
};
struct polygon {
vector<point> p;
int pre(int i) const { return i == 0 ? p.size() - 1 : i - 1; }
int nxt(int i) const { return i == p.size() - 1 ? 0 : i + 1; }
int area() { //area * 2
int sum = 0;
for (int i = 0; i < p.size(); i++) {
sum += p[i] ^ p[nxt(i)];
}
return sum;
}
template<typename F> void rotcaliper(const F &func) const {
const auto &p = this->p;
const auto area = [](const point &u, const point &v, const point &w) {
return (w - u) ^ (w - v);
};
for (int i = 0, j = 1; i < p.size(); i++) {
const auto nxti = this->nxt(i);
func(p[i], p[nxti], p[j]);
while (area(p[this->nxt(j)], p[i], p[nxti]) >= area(p[j], p[i], p[nxti])) {
j = this->nxt(j);
func(p[i], p[nxti], p[j]);
}
}
}
int diameter2() const {
const auto &p = this->p;
if (p.size() == 1) return 0;
if (p.size() == 2) return p[0].dis2(p[1]);
int ans = 0;
auto func = [&] (const point &u, const point &v, const point &w) {
ans = max({ans, w.dis2(u), w.dis2(v)});
};
rotcaliper(func);
return ans;
}
};
void solve() {
cin >> n;
polygon p;
p.p.resize(n);
for (int i = 0; i < n; i++) {
cin >> p.p[i].x >> p.p[i].y;
}
for (int i = 0; i < n; i++) {
int j = (i + 1) % n;
f[i][j] = p.p[i].dis2(p.p[j]);
}
for (int k = 3; k <= n; k++) {
for (int i = 0; i < n; i++) {
int j = (i + k - 1) % n;
f[i][j] = max({f[(i + 1) % n][j], f[i][(j - 1 + n) % n], p.p[i].dis2(p.p[j])});
}
}
int ans = inf;
for (int i = 0; i < n; i++) {
int pre = (i - 1 + n) % n, nxt = (i + 1) % n;
for (int j = 0; j < n; j++) {
if (((p.p[j] - p.p[i]) ^ (p.p[pre] - p.p[i])) == 0) continue;
if (((p.p[j] - p.p[i]) ^ (p.p[nxt] - p.p[i])) == 0) continue;
ans = min(ans, f[i][j] + f[j][i]);
}
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
int T = 1; cin >> T;
while (T--) {
solve();
}
return 0;
}
Canvas(tarjan强连通分量建图)
有一个长度为 \(n\) 的序列。开始时,序列中的所有元素都等于 \(0\)。也有\(m\)操作,其中\(i\)-th 操作会将序列中\(l_i\)-th 元素的值改为\(x_i\),也会将序列中\(r_i\)-th 元素的值改为\(y_i\)。每个操作必须准确地执行一次。
找出执行操作的最佳顺序,以便在所有操作之后,序列中所有元素的和都达到最大值。
//Solution by SUA
#include <bits/stdc++.h>
#define MAXN ((int) 5e5)
#define MAXM ((int) 5e5)
using namespace std;
int n, m, OP[MAXM + 10][4];
vector<int> ans;
vector<int> e[MAXN + 10], v[MAXN + 10];
// bel[i]:点 i 属于哪个强连通分量
int clk, bCnt, dfn[MAXN + 10], low[MAXN + 10], bel[MAXN + 10];
bool inStk[MAXN + 10];
stack<int> stk;
// deg[i]:强连通分量 i 的入度
int deg[MAXN + 10];
// vis[i]:dfs 构造答案的过程中,节点 i 是否被访问过
bool vis[MAXN + 10];
// tarjan 求强连通分量
void tarjan(int sn) {
low[sn] = dfn[sn] = ++clk;
stk.push(sn); inStk[sn] = true;
for (int fn : e[sn]) {
if (!dfn[fn]) {
tarjan(fn);
low[sn] = min(low[sn], low[fn]);
} else if (inStk[fn]) {
low[sn] = min(low[sn], dfn[fn]);
}
}
if (dfn[sn] == low[sn]) {
++bCnt;
while (stk.top() != sn) {
bel[stk.top()] = bCnt;
inStk[stk.top()] = false;
stk.pop();
}
bel[stk.top()] = bCnt;
inStk[stk.top()] = false;
stk.pop();
}
}
// 从节点 sn 开始 dfs,并按 dfs 序将访问过的每条边加入 vec 里
void dfs(int sn, vector<int> &vec) {
vis[sn] = true;
for (int i = 0; i < e[sn].size(); i++) {
int fn = e[sn][i];
int val = v[sn][i];
vec.push_back(val);
if (!vis[fn]) dfs(fn, vec);
}
}
void solve() {
scanf("%d%d", &n, &m);
vector<int> one, two;
for (int i = 1; i <= n; i++) e[i].clear(), v[i].clear();
for (int i = 1; i <= m; i++) {
for (int j = 0; j < 4; j++) scanf("%d", &OP[i][j]);
if (OP[i][1] == 1 && OP[i][3] == 1) {
// 两个数都改成 1,最差的操作
one.push_back(i);
} else if (OP[i][1] == 2 && OP[i][3] == 2) {
// 两个数都改成 2,最好的操作
two.push_back(i);
} else if (OP[i][1] == 1) {
// 图中加一条从 1 指向 2 的边
e[OP[i][0]].push_back(OP[i][2]);
v[OP[i][0]].push_back(i);
} else {
// 图中加一条从 1 指向 2 的边
e[OP[i][2]].push_back(OP[i][0]);
v[OP[i][2]].push_back(i);
}
}
// 顺序输出的答案中,两个数都改成 1 的最差操作最先输出
ans.clear();
for (int x : one) ans.push_back(x);
memset(dfn, 0, sizeof(int) * (n + 3));
clk = bCnt = 0;
for (int sn = 1; sn <= n; sn++) if (!dfn[sn]) tarjan(sn);
memset(deg, 0, sizeof(int) * (n + 3));
for (int sn = 1; sn <= n; sn++) {
for (int fn : e[sn]) if (bel[sn] != bel[fn]) deg[bel[fn]]++;
}
vector<int> vec;
memset(vis, 0, sizeof(bool) * (n + 3));
// 如果一个入度为 0 的强连通分量受一个 (l_i, 2, r_i, 2) 操作影响,那么需要从 l_i 或 r_i 开始 dfs
for (int x : two) for (int j = 0; j < 4; j += 2) {
int sn = OP[x][j];
if (deg[bel[sn]] == 0 && !vis[sn]) dfs(sn, vec);
}
// 剩下的入度为 0 的强连通分量,随便找一个点开始 dfs
for (int sn = 1; sn <= n; sn++) {
if (deg[bel[sn]] == 0 && !vis[sn]) dfs(sn, vec);
}
// dfs 序是答案的倒序
reverse(vec.begin(), vec.end());
ans.insert(ans.end(), vec.begin(), vec.end());
// 顺序输出的答案中,两个数都改成 2 的最好的操作最后输出
for (int x : two) ans.push_back(x);
// 计算序列最终的和
unordered_map<int, int> mp;
for (int x : ans) {
mp[OP[x][0]] = OP[x][1];
mp[OP[x][2]] = OP[x][3];
}
int tot = 0;
for (auto it = mp.begin(); it != mp.end(); it++) tot += it->second;
printf("%d\n", tot);
for (int i = 0; i < m; i++) printf("%d%c", ans[i], "\n "[i + 1 < m]);
}
int main() {
int tcase; scanf("%d", &tcase);
while (tcase--) solve();
return 0;
}
20230716
ABC310F_Make 10 Again(状压概率dp)
题意
有 n 个骰子,每个随机产生 [1,Ai] 的点数。问存在一个子集点数和为10的概率。
n <= 100, Ai <= 1e6
题解
//
// Created by blackbird on 2023/7/15.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int mod = 998244353, mx = (1 << 11) - 1;
int n, a[233], inv[233], dp[233][2333];
int pow(int a, int b) {
a %= mod; int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
inv[i] = pow(a[i], mod - 2);
}
dp[0][1 << 0] = 1;
for (int i = 1; i <= n; i++) {
for (int s = 0; s <= mx; s++) {
for (int k = 1; k <= min(a[i], 10ll); k++) {
(dp[i][(s | s << k) & mx] += dp[i - 1][s] * inv[i] % mod) %= mod;
}
if (a[i] > 10) {
(dp[i][s] += dp[i - 1][s] * (a[i] - 10) % mod * inv[i] % mod) %= mod;
}
}
}
int ans = 0;
for (int s = (1 << 10); s <= mx; s++)
ans = (ans + dp[n][s]) % mod;
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); srand(time(0));
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
20230718
E - Test(KMP)
题意:求最短的字符串,使得给定的三个字符串均为它的子串。
题解:枚举前后顺序,判定是否存在包含关系,如果不互相包含,则答案为字符串拼接后减去最长公共前后缀。所有操作都可以用kmp实现。
//
// Created by blackbird on 2023/7/18.
//
#include <bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10, inf = 0x3f3f3f3f;
void getnext(char p[], int n, int ne[]) {
for (int i = 2, j = 0; i <= n; i++) {
while (j && p[j + 1] != p[i]) j = ne[j];
if (p[j + 1] == p[i]) j++;
ne[i] = j;
}
}
int kmp(char s[], int m, char p[], int n, int ne[]) {
int j = 0;
for (int i = 1; i <= m; i++) {
while (j && p[j + 1] != s[i]) j = ne[j];
if (p[j + 1] == s[i]) j++;
if (j == n) {
return -1;
}
}
return j;
}
char s[4][N];
int ne[4][N], f[4][4], len[4];
signed main() {
for (int i = 1; i <= 3; i++) {
cin >> s[i] + 1; len[i] = strlen(s[i] + 1);
getnext(s[i], len[i], ne[i]);
}
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i == j) continue;
f[j][i] = kmp(s[j], len[j], s[i], len[i], ne[i]);
}
}
int ans = inf;
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
for (int k = 1; k <= 3; k++) {
if (i == j || j == k || i == k) continue;
if (f[i][j] >= 0 && f[j][k] >= 0)
ans = min(ans, len[i] + len[j] + len[k] - f[i][j] - f[j][k]);
else {
if (f[i][j] < 0 && f[i][k] < 0) ans = min(ans, len[i]);
else if (f[i][j] < 0) ans = min(ans, len[i] + len[k] - f[i][k]);
else if (f[j][k] < 0) ans = min(ans, len[i] + len[j] - f[i][j]);
}
}
}
}
cout << ans << "\n";
return 0;
}
B - Tree Array(概率论好题)
题意:给定一棵树,结点编号1~n。
第一步任选一个点染色,此后每一步,从与已染色结点相邻的未染色结点的集合中,任选一个点染色。
问依次染色的结点编号构成的序列,逆序对数量的期望值。
\(n\le 200\)
题解:
枚举每一个点作为根节点,再枚举每个逆序对\((i,j),i>j\)对期望的贡献。
不难发现,\(i\)在序列中先于\(j\)出现的概率,只与\(i,j\)和它们最近公共祖先之间的深度差有关。这个概率可以直接递推预处理。
使用倍增求LCA,时间复杂度\(O(n^3\log n)\)。也可以用tarjan做到\(O(n^3)\)
// LUOGU_RID: 116293692
//
// Created by blackbird on 2023/7/18.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 3e2 + 10, mod = 1e9 + 7;
int n;
vector<int> g[N];
int d[N], f[N][31];
void dfs(int u, int fa) {
d[u] = d[fa] + 1; f[u][0] = fa;
for (int i = 1; i <= 25; i++)
f[u][i] = f[f[u][i-1]][i-1];
for (auto v : g[u])
if (v != fa) dfs(v, u);
}
int lca(int x, int y) {
if (d[x] < d[y]) swap(x, y);
int k = d[x] - d[y];
for (int i = 25; i >= 0; i--)
if ((1 << i) & k) x = f[x][i];
if (x == y) return x;
for (int i = 25; i >= 0; i--)
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
return f[x][0];
}
int p[N][N];
int pow(int a, int b) {
a %= mod; int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int inv(int x) {
return pow(x, mod - 2);
}
void solve() {
cin >> n;
for (int i = 1; i <= n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v); g[v].push_back(u);
}
// p[x][y] : x比y先走完的概率
for (int i = 1; i <= n; i++) p[0][i] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
p[i][j] = (p[i - 1][j] + p[i][j - 1]) * inv(2) % mod;
}
}
int ans = 0;
for (int rt = 1; rt <= n; rt++) {
for (int i = 1; i <= n; i++) {
d[i] = 0;
memset(f[i], 0, sizeof f[i]);
}
dfs(rt, 0);
// i > j
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
int x = lca(i, j);
ans = (ans + p[d[i] - d[x]][d[j] - d[x]]) % mod;
}
}
}
ans = ans * inv(n) % mod;
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0); srand(time(0));
int T = 1; //cin >> T;
while (T--) {
solve();
}
return 0;
}
20230722
yLOI2022 长安幻世绘(线段树维护连续颜色段)
题意
你需要在元素互不相同的数列 a 中选出一个长度为 m 的元素互不相邻的子列,使得子列的极差最小。 \(1 \leq n \leq 10^5\),\(1 \leq m \leq \lceil\frac{n}{2}\rceil\),\(1 \leq a_i \leq 10^9\)。
题解
//
// Created by blackbird on 2023/7/22.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10, inf = 1e9 + 10;
int n, m;
pii a[N];
struct node {
int sum, len, llen, rlen;
} tr[N];
#define mid (l+r>>1)
#define ls (u<<1)
#define rs (u<<1|1)
void build(int l, int r, int u) {
tr[u].len = r - l + 1;
if (l == r) {
return;
}
build(l, mid, ls);
build(mid + 1, r, rs);
}
void pushup(int u) {
tr[u].sum = tr[ls].sum + tr[rs].sum - ((tr[ls].rlen & 1) && (tr[rs].llen & 1));
tr[u].llen = tr[ls].llen + (tr[ls].llen == tr[ls].len) * tr[rs].llen;
tr[u].rlen = tr[rs].rlen + (tr[rs].rlen == tr[rs].len) * tr[ls].rlen;
}
void upd(int pos, int l, int r, int u, int x) {
if (l == r) {
tr[u].sum = tr[u].llen = tr[u].rlen = x;
return;
}
if (pos <= mid) upd(pos, l, mid, ls, x);
else upd(pos, mid + 1, r, rs, x);
pushup(u);
}
signed main() {
//cin.tie(nullptr)->sync_with_stdio(false); srand(time(0));
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i].first;
a[i].second = i;
}
sort(a + 1, a + 1 + n);
build(1, n, 1);
int ans = inf, r = 0;
for (int l = 1; l <= n; l++) {
while (tr[1].sum < m && r <= n) {
r++;
upd(a[r].second, 1, n, 1, 1);
}
if (r > n) break;
ans = min(ans, a[r].first - a[l].first);
upd(a[l].second, 1, n, 1, 0);
}
cout << ans << "\n";
return 0;
}