Loading

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;
}

posted @ 2025-07-25 21:09  qkhm  阅读(14)  评论(0)    收藏  举报