MX galaxy Day12
climb
注意到公差 \(d\) 只可能 \(0\sim \frac{m-1}{n-1}\) 。
\(O(n)\) 处理最长的不用修改的长度。
具体的,如果 \(h_i + (j - i) \times d = h_j\) , 则 \(h_i-d\times i = h_j-d\times j\) 。
故按照 \(h_x - d\times x\) 划分等价类,找到最大的等价类大小为 \(t\) ,则答案为 \(n-t\) 。
注意特判 \(n=1\) 。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 1e7 + 7;
typedef long long ll;
int T, n, m, h[_], ans, dp[_], D;
template <typename T>inline void read(T &x)
{
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c-'0');
x=(f?-x:x);
}
int main() {
#ifndef DEBUG
freopen("climb.in", "r", stdin);
freopen("climb.out","w",stdout);
#endif
read(T);
while (T--) {
read(n), read(m);
lep(i, 1, n) read(h[i]); D = (m - 1) / (n - 1) * n - 1;
rep(y, (m - 1) / (n - 1), 0) {
lep(i, 1, n) {
++dp[D + h[i] - y * i];
if (h[i] - (i - 1) * y > 0 and h[i] + (n - i) * y <= m)
ans = std::max(ans, dp[D + h[i] - y * i]);
}
lep(i, 1, n) dp[D + h[i] - y * i] = 0;
}
printf("%d\n", n - ans); ans = 0;
}
return 0;
}
rain
我们先假设原图是棵树,思考先把所有的点全部淹了。
这样所有的点水位线都相等,然后再慢慢地降低水位线。
之后水位线会低于最高的挡板,然后树就被分成了两个不相干的连通块。
按照这样的思想,我们将树边按照大小考虑,每次合并相邻的两个连通块的答案即可。
具体的,记每个连通块的最高水位线和当前的方案数,每次合并时,先将水位线补齐,然后相乘即可。
那么如果原图不是树呢,发现只要按照 \(Kruskal\) 最小生成树的构造过程加入边即可。
因为多余的边一定不会对连通块有影响,在它加入之前连通块已经被淹平了。
另外补充一点,官解是 \(Kruskal\) 重构树,上述过程等价于在重构树上 \(DP\) 合并。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 2e5 + 7;
const int mod = 998244353;
typedef long long ll;
struct edge { int u, v, w; }e[_];
int n, m, H, fa[_], val[_]; ll dp[_];
int fnd(int x) { return fa[x] == x ? x : fa[x] = fnd(fa[x]); }
int main() {
#ifndef DEBUG
freopen("rain.in", "r", stdin);
freopen("rain.out","w",stdout);
#endif
scanf("%d%d%d", & n, & m, & H); int u, v, w;
lep(i, 1, n + 1) fa[i] = i, val[i] = -1;
lep(i, 1, m) scanf("%d%d%d", & u, & v, & w), e[i] = { u, v, w };
e[++m] = { 1, n + 1, H }, val[++n] = H - 1;
std::sort(e + 1, e + 1 + m, [](const edge& x, const edge& y) { return x.w < y.w; });
lep(i, 1, m) { u = fnd(e[i].u), v = fnd(e[i].v), w = e[i].w;
if (u == v) continue;
dp[u] = (dp[u] + w - val[u]) % mod, dp[v] = (dp[v] + w - val[v]) % mod;
fa[u] = v, val[v] = w, dp[v] = dp[u] * dp[v] % mod;
}
printf("%lld\n", dp[fnd(n)]);
return 0;
}
stone
\(O(n^4)\) 的暴力 \(DP\) 是容易的。
然后发现迈左脚和右脚的代价不相关,完全可以分开枚举转移。
具体的,设
\(f[i, j]\) 表示 两脚分别在 \(i, j\) ,接下来应该迈左脚的最小代价。
\(g[i, j]\) 表示两脚分别在 \(i, j\) ,接下来应该迈右脚的最小代价。
最终答案为 \(f[n+1, n+1]\) 。
复杂度 \(O(n^3)\) 。
观察转移式, \(g[i, j] = \min\{f[a, j] + (a - i)^2\}\) ,具有斜率优化的形式,李超树维护即可。
复杂度 \(O(n^2\log n)\) 。
注意 \(g[0, i]\) 并不是一个合法状态,不能插入李超树。
如果使用单调队列维护凸包可以做到 \(O(n^2)\) 。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 3000 + 7;
const int __ = _ * _ * 1.5;
const int inf = 1.5e9;
typedef long long ll;
int n, A[_], B[_], f[_][_], g[_][_];
int rt[_ << 1], idx, seg[__], ls[__], rs[__];
int k[__ << 1], b[__ << 1], tot;
#define md ((s + t) >> 1)
int calc(int x, int d) { if (!x) return inf; return k[x] * d + b[x]; }
int upd(int u, int v, int d) { if (!u or !v) return u | v; return calc(u, d) < calc(v, d) ? u : v; }
void mdy(int s, int t, int v, int* p) {
while (true) {
if (!v) break; if (!*p) *p = ++idx; int& u = seg[*p];
if (upd(u, v, md) == v) std::swap(u, v);
if (s == t or !v) break;
if (upd(u, v, s) == v) t = md, p = &ls[*p];
else if (upd(u, v, t) == v) s = md + 1, p = &rs[*p];
else break;
}
}
int qry(int s, int t, int d, int p) { int res = 0;
while (true) {
if (!seg[p]) break;
res = upd(res, seg[p], d);
if (s == t) break;
d <= md ? (t = md, p = ls[p]) : (s = md + 1, p = rs[p]);
}
return res;
}
void Ins(int p, int _k, int _b) { k[++tot] = _k, b[tot] = _b; mdy(1, n + 1, tot, &rt[p]); }
int Qry(int p, int d) { return calc(qry(1, n + 1, d, rt[p]), d); }
#undef md
int main() { b[0] = inf;
scanf("%d", & n);
lep(i, 0, n + 1) scanf("%d", A + i);
lep(i, 0, n + 1) scanf("%d", B + i);
Ins(0, 0, 0);
lep(i, 0, n + 1) {
lep(j, 0, n + 1) {
if (i) g[i][j] = Qry(j, i) + i * i;
if (A[i] == B[j]) f[i][j] = Qry(i + n + 2, j) + j * j,
Ins(j, -2 * i, f[i][j] + i * i);
if (i) Ins(i + n + 2, -2 * j, g[i][j] + j * j);
}
}
printf("%d\n", f[n + 1][n + 1]);
return 0;
}
flower
如果考虑启发式合并就倒闭了。
先考虑暴力添加操作,如果直接将 \(x\) 向 \(y\) 连边,那么 \(z\) 再变成 \(x\) 就倒闭了。
所以对于操作 \((x, y)\) ,将 \(x\) 与 \(y\) 指向一个共同的虚点 \(p\) ,表示这两个点具有同样的颜色。
如果对应颜色已经不存在了直接对应位置挂空即可。
问题可以转化成相邻两个位置的颜色相同的对数。
在一个操作后缀组成的森林里, \(x\) 与 \(x+1\) 对应虚点的操作之后的所有询问,这样的对数 \(+1\) 。
然后查询就是在操作区间右端点单点查值, \(BIT\) 即可。
下面考虑如何将后缀向左扩展一位。
假设现在加入了当前时间轴之前的一个操作 \((x, y)\) 。
我们知道,每个位置都在森林的叶子上,所以将 \(y\) 的位置换成一个虚点,将 \(x\) , \(y\) 挂在虚点下面即可。
然后维护答案的变化量以及求 \(LCA\) 需要的树的信息。
这个做法的依据是,之后 \(y\) 进行的所有合并变成了 \(x\) 和 \(y\) 一起的合并。
如果 \(x\) 本来就有一个父亲,这里就直接断开了。
因为更早的时间里 \(x\) 已经变成 \(y\) 了,之后 \(x\) 进行的操作自然无效了。
点击查看
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
const int _ = 2e6 + 7;
typedef long long ll;
typedef std::pair<int, int> PII;
int n, m, q, x[_], y[_], c[_], fa[_][21], ans[_], id[_], tot, dep[_];
std::vector <PII> o[_];
void add(int x, int k) { if (!x) return; while (x <= m) c[x] += k, x += x & -x; }
int Qry(int x) { int res = 0; while (x) res += c[x], x -= x & -x; return res; }
int lca(int x, int y) {
if (dep[x] < dep[y]) std::swap(x, y);
rep(i, 20, 0) if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if (x == y) return x;
rep(i, 20, 0) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
void del(int x) { if (x > 1) add(id[lca(x, x - 1)], -1); if (x < n) add(id[lca(x, x + 1)], -1); }
void add(int x) { if (x > 1) add(id[lca(x, x - 1)], 1); if (x < n) add(id[lca(x, x + 1)], 1); }
void Add(int t) {
del(x[t]);
fa[++tot][0] = fa[y[t]][0], fa[y[t]][0] = fa[x[t]][0] = tot, id[tot] = t;
dep[tot] = dep[fa[tot][0]] + 1, dep[x[t]] = dep[y[t]] = dep[tot] + 1;
lep(i, 1, 20) fa[tot][i] = fa[fa[tot][i - 1]][i - 1],
fa[x[t]][i] = fa[fa[x[t]][i - 1]][i - 1], fa[y[t]][i] = fa[fa[y[t]][i - 1]][i - 1];
add(x[t]);
}
int main() {
scanf("%d%d%d", & n, & m, & q); tot = n;
lep(i, 1, m) scanf("%d%d", x + i, y + i); int l, r;
lep(i, 1, q) {
scanf("%d%d", & l, & r);
o[l].push_back({r, i});
}
rep(i, m, 1) {
Add(i);
for (auto t : o[i]) ans[t.second] = n - Qry(t.first);
}
lep(i, 1, q) printf("%d\n", ans[i]);
return 0;
}
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下

分开枚举!分开枚举!分开枚举!
浙公网安备 33010602011771号