jack pack 1.
收录数据结构题
1. CF1920F2 - Smooth Sailing (Hard Version)
tag:kruskal 重构树;link。
观察:选取中间岛最下侧的一个点,将这个点右下角与边界上点连边。
则一条路径绕过整个岛等价于跨过这条边偶数次。那么每个点拆成
bfs 可以求出每个点到最近火山的距离,最大点权瓶颈路即为答案。使用 kruskal 重构树求解。
点击查看代码
//CF1920F2 #include <bits/stdc++.h> using namespace std; const int N = 3e6 + 10; const int dx[4] = {-1, 0, 0, 1}, dy[4] = {0, -1, 1, 0}; int n, m, q, mx = 0, my = 0; vector<char> s[N]; vector<int> dis[N]; vector<int> g[N]; char st[N]; struct ege{ int u, v, w; } eg[N]; int tot = 0; int fa[N], nd[N]; int ndc = 0; int val[N]; int cg(int x, int y, int id){ return id * n * m + (x-1) * m + y; } void add(int p, int q, int r){ eg[++tot] = { p, q, r }; } bool cmp(ege x, ege y){ return x.w > y.w; } int gf(int x){ return x == fa[x] ? x : fa[x] = gf(fa[x]); } int dep[N], anc[N][20]; void dfs(int x, int fat){ dep[x] = dep[fat] + 1; anc[x][0] = fat; for(int i = 1; i < 20; ++ i){ anc[x][i] = anc[anc[x][i-1]][i-1]; } for(int i : g[x]){ dfs(i, x); } } int lca(int x, int y){ if(dep[x] > dep[y]){ swap(x, y); } for(int i = 19; i >= 0; -- i){ if(dep[anc[y][i]] >= dep[x]){ y = anc[y][i]; } } if(x == y){ return y; } for(int i = 19; i >= 0; -- i){ if(anc[x][i] != anc[y][i]){ x = anc[x][i]; y = anc[y][i]; } } return anc[x][0]; } int main(){ scanf("%d%d%d", &n, &m, &q); queue<pair<int, int> > qu; for(int i = 1; i <= n; ++ i){ s[i].resize(m+1); dis[i].resize(m+1); scanf("%s", st + 1); for(int j = 1; j <= m; ++ j){ s[i][j] = st[j]; dis[i][j] = -1; if(s[i][j] == 'v'){ qu.push(make_pair(i, j)); dis[i][j] = 0; } else if(s[i][j] == '#'){ if(i > mx){ mx = i; my = j; } } } } while(!qu.empty()){ int x = qu.front().first; int y = qu.front().second; qu.pop(); for(int i = 0; i < 4; ++ i){ int xx = x + dx[i], yy = y + dy[i]; if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && dis[xx][yy] == -1){ dis[xx][yy] = dis[x][y] + 1; qu.push(make_pair(xx, yy)); } } } for(int i = 1; i <= n; ++ i){ for(int j = 1; j <= m; ++ j){ fa[cg(i, j, 0)] = nd[cg(i, j, 0)] = cg(i, j, 0); fa[cg(i, j, 1)] = nd[cg(i, j, 1)] = cg(i, j, 1); if(s[i][j] == '#'){ continue; } for(int k = 2; k < 4; ++ k){ int x = i + dx[k], y = j + dy[k]; if(x >= 1 && x <= n && y >= 1 && y <= m && s[x][y] != '#'){ if(j == y && j > my && min(i, x) == mx){ add(cg(i, j, 0), cg(x, y, 1), min(dis[i][j], dis[x][y])); add(cg(i, j, 1), cg(x, y, 0), min(dis[i][j], dis[x][y])); } else { add(cg(i, j, 0), cg(x, y, 0), min(dis[i][j], dis[x][y])); add(cg(i, j, 1), cg(x, y, 1), min(dis[i][j], dis[x][y])); } } } } } ndc = cg(n, m, 1); sort(eg + 1, eg + tot + 1, cmp); for(int i = 1; i <= tot; ++ i){ int u = eg[i].u, v = eg[i].v, w = eg[i].w; if(gf(u) == gf(v)){ continue; } u = gf(u), v = gf(v); fa[u] = v; ++ ndc; g[ndc].push_back(nd[u]); g[ndc].push_back(nd[v]); nd[v] = ndc; val[ndc] = w; } dfs(ndc, 0); while(q--){ int x, y; scanf("%d%d", &x, &y); int st = cg(x, y, 0), ed = cg(x, y, 1); printf("%d\n", val[lca(st, ed)]); } return 0; }
2. qoj5165/NOIP2022 - 比赛
tag:历史和线段树;link。
考虑扫描线,在
设
考虑
- 令
更新为 。可以使用单调栈转化为若干个 的区间加; - 令
更新为 。可以使用单调栈转化为若干个 的区间加; - 对于所有
,令 加上 ,即维护 的历史和。
不会历史和怎么办?直接矩阵维护!
线段树每个节点维护如下行向量,非叶节点定义为所有叶节点行向量的和:
那么每次
维护矩阵 tag 即可。
但是太慢了!卡常方法:
- 循环展开;
- 维护
bool
表示 tag 是否为单位矩阵; - 发现矩阵只有
个位置有用。记录下来,矩阵乘法时只更新这些部分。
点击查看代码(卡常版)
//qoj5165 #include <bits/stdc++.h> using namespace std; const int N = 2.5e5 + 10; int T, n, Q, sa[N], sb[N]; typedef unsigned long long ll; ll a[N], b[N], ans[N]; vector<pair<int, int> > q[N]; struct mat{ ll A, B, C, D, E, F, G, H, I; mat operator * (const mat &b) const { mat c; c.A = A + b.A; c.B = B + b.B + A * b.E; c.C = C + b.C; c.D = D + b.D + C * b.E; c.E = E + b.E; c.F = F + b.F; c.G = G + b.G; c.H = H + b.H + F * b.A + G * b.C; c.I = I + b.I + F * b.B + G * b.D + H * b.E; return c; } } ini; mat ma(ll x){ mat k = ini; k.C = k.F = x; return k; } mat mb(ll x){ mat k = ini; k.A = k.G = x; return k; } mat lsh(){ mat k = ini; k.E = 1; return k; } struct node{ ll x, y, xy, all, cnt; bool flg; mat tag; void mdf(mat val){ all = x * val.B + y * val.D + xy * val.E + all + cnt * val.I; xy = x * val.A + y * val.C + xy + cnt * val.H; x += cnt * val.F; y += cnt * val.G; } } t[N*4]; void psd(int p){ if(!t[p].flg){ return; } t[p].flg = 0; t[p<<1].mdf(t[p].tag); t[p<<1|1].mdf(t[p].tag); t[p<<1].tag = t[p<<1].tag * t[p].tag; t[p<<1|1].tag = t[p<<1|1].tag * t[p].tag; t[p<<1].flg = t[p<<1|1].flg = 1; t[p].tag = ini; } void upd(int p){ t[p].x = t[p<<1].x + t[p<<1|1].x; t[p].y = t[p<<1].y + t[p<<1|1].y; t[p].xy = t[p<<1].xy + t[p<<1|1].xy; t[p].all = t[p<<1].all + t[p<<1|1].all; t[p].cnt = t[p<<1].cnt + t[p<<1|1].cnt; } void build(int p, int l, int r){ t[p].cnt = r - l + 1; t[p].flg = 0; if(l != r){ int mid = l + r >> 1; build(p<<1, l, mid); build(p<<1|1, mid+1, r); upd(p); } } void add(int p, int l, int r, int ql, int qr, mat val){ if(qr < l || r < ql){ return; } else if(ql <= l && r <= qr){ t[p].mdf(val); t[p].tag = t[p].tag * val; t[p].flg = 1; } else { int mid = l + r >> 1; psd(p); add(p<<1, l, mid, ql, qr, val); add(p<<1|1, mid+1, r, ql, qr, val); upd(p); } } ll ask(int p, int l, int r, int ql, int qr){ if(qr < l || r < ql){ return 0; } else if(ql <= l && r <= qr){ return t[p].all; } else { int mid = l + r >> 1; psd(p); return ask(p<<1, l, mid, ql, qr) + ask(p<<1|1, mid+1, r, ql, qr); } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> T >> n; for(int i = 1; i <= n; ++ i){ cin >> a[i]; } for(int i = 1; i <= n; ++ i){ cin >> b[i]; } cin >> Q; for(int i = 1; i <= Q; ++ i){ int l, r; cin >> l >> r; q[r].emplace_back(l, i); } build(1, 1, n); int ta = 0, tb = 0; for(int i = 1; i <= n; ++ i){ int la = i, lb = i; add(1, 1, n, i, i, ma(a[i])); while(ta && a[sa[ta]] <= a[i]){ add(1, 1, n, sa[ta], la-1, ma(a[i]-a[sa[ta]])); la = sa[ta]; -- ta; } a[la] = a[i]; sa[++ta] = la; add(1, 1, n, i, i, mb(b[i])); while(tb && b[sb[tb]] <= b[i]){ add(1, 1, n, sb[tb], lb-1, mb(b[i]-b[sb[tb]])); lb = sb[tb]; -- tb; } b[lb] = b[i]; sb[++tb] = lb; add(1, 1, n, 1, n, lsh()); for(auto j : q[i]){ ans[j.second] = ask(1, 1, n, j.first, i); } } for(int i = 1; i <= Q; ++ i){ cout << ans[i] << '\n'; } return 0; }
3. qoj361/JOISC2017 - 港湾設備 (Port Facility)
tag:优化建图;link。
若两个区间有交但不包含,则这两个区间之间连一条边;那么若图不是二分图则答案为
考虑优化建图。但是这里的优化建图与平时的不同,不是加点使边数更少;而是不加一些不必要的边。
将区间从左到右排序。遍历到一个区间
原因是:在遍历到一个线段树节点后,这个线段树节点内所有边都两两增加了一条偶路径;那么再次遍历节点时,若只连向其中一个,则依旧新区间与节点内有一条奇偶性相同的路径。
点击查看代码
//qoj361 #include <bits/stdc++.h> using namespace std; const int N = 2e6 + 10; const long long P = 1e9 + 7; int n, col[N], tot; pair<int, int> a[N]; vector<int> t[N*4], g[N]; void ask(int p, int l, int r, int ql, int qr, int x){ if(qr < l || r < ql){ return; } else if(ql <= l && r <= qr){ for(int i : t[p]){ g[x].push_back(i); g[i].push_back(x); } while(t[p].size() > 1){ t[p].pop_back(); } } else { int mid = l + r >> 1; ask(p<<1, l, mid, ql, qr, x); ask(p<<1|1, mid+1, r, ql, qr, x); } } void add(int p, int l, int r, int x, int v){ t[p].push_back(v); if(l != r){ int mid = l + r >> 1; if(x <= mid){ add(p<<1, l, mid, x, v); } else { add(p<<1|1, mid+1, r, x, v); } } } void dfs(int x, int cl){ col[x] = cl; for(int i : g[x]){ if(!col[i]){ dfs(i, 3 - cl); } } } int main(){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ scanf("%d%d", &a[i].first, &a[i].second); } sort(a + 1, a + n + 1); for(int i = 1; i <= n; ++ i){ ask(1, 1, n + n, a[i].first, a[i].second, i); add(1, 1, n + n, a[i].second, i); } for(int i = 1; i <= n; ++ i){ if(!col[i]){ ++ tot; dfs(i, 1); } } for(int i = 1; i <= n; ++ i){ for(int j : g[i]){ if(col[i] == col[j]){ puts("0"); return 0; } } } long long ans = 1; while(tot--){ ans = ans * 2 % P; } printf("%lld\n", ans); return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步