Petrozavodsk Summer 2019. Day 9. MEX Foundation Contest【杂题】

比赛链接

A. The One Polynomial Man

给定模数 \(p\)\(0 \sim p - 1\) 的两个集合 \(U,V\),求有多少个有序对 \((a,b)\) 满足:

  • \(f(a,b) = \prod\limits_{z \in V} \left( \frac{(2a+3b)^2 + 5a^2}{(3a+b)^2} + \frac{(2a+5b)^2 + 3b^2}{(3a+2b)^2} - z\right) \equiv 0 \pmod p\)

  • \(a \in U\)

  • \(b \in U\)

\(2 \le p \le 10^6\),保证 \(p\) 为素数。


首先 \(a=0\)\(b=0\) 的答案我们可以简单求出。不妨假设 \(0 \notin U\)

注意到给定的式子中 \(a,b\) 是齐次的。这意味着如果 \((a,b)\) 合法,那么 \((ta,tb)\) 也合法。

通过枚举 \(\frac{a}{b}\),我们可以求出一个 \(0 \sim p - 1\) 的集合 \(L\),满足 \(f(a,b) \equiv 0 \pmod p\) 当且仅当 \(\frac{a}{b} \in L\)。现在问题变成:求有多少对 \(a,b \in U\) 满足 \(a \times b^{-1} \in L \pmod p\)

考虑如果是 \(a+b \in L\) 的话就可以直接卷积了。考虑想个办法把乘法变成加法。取 \(p\) 的原根 \(g\),那么 \(a \times b^{-1} \in L\)\(g^x \times g^{-y} = g^{x-y} \in L\),于是就可以直接 FFT 算了。时间复杂度 \(\mathcal{O}(p \log p)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define int long long
#define fi first
#define se second
#define poly vector <int>
constexpr int N = 1e6 + 5; 
bool Mbe;
int ksm(int a, int b, int mod) {
	int res = 1;
	for (; b; b >>= 1, a = 1LL * a * a % mod) if (b & 1) res = 1LL * res * a % mod;
	return res;
}
int inv(int a, int mod) {
	return ksm(a, mod - 2, mod);
}
namespace NTT {
	constexpr int mod = 998244353, g = 3, ig = 332748118, N = 3e6 + 5;
	int rev[N];
	void fft(poly &a, int L, int c) {
		for (int i = 0; i < L; i++) if (i > rev[i]) swap(a[i], a[rev[i]]);
		for (int i = 1; i < L; i <<= 1) {
			int w = ksm(c, (mod - 1) / (i << 1), mod);
			for (int j = 0; j < L; j += i << 1) {
				int coef = 1;
				for (int k = 0; k < i; k++) {
					int val = 1LL * coef * a[i | j | k] % mod;
					a[i | j | k] = (a[j | k] + mod - val) % mod;
					a[j | k] = (a[j | k] + val) % mod;
					coef = 1LL * coef * w % mod;
				}
			}
		} 
	}
	inline poly mul(poly A, poly B) {
		int L = 1, n = (int)A.size() - 1, m = (int)B.size() - 1;
		while (L <= n + m) L <<= 1;
		A.resize(L);
		B.resize(L);
		for (int i = 1; i < L; i <<= 1)
			for (int j = 0; j < i; j++) rev[i + j] = rev[j] + L / (i << 1);
		fft(A, L, g);
		fft(B, L, g);
		poly C(L);
		for (int i = 0; i < L; i++) C[i] = 1LL * A[i] * B[i] % mod;
		fft(C, L, ig);
		int iv = inv(L, mod);
		for (int i = 0; i < L; i++) C[i] = 1LL * C[i] * iv % mod;
		C.resize(n + m + 1);
		return C;
	}
}
vector <int> getdiv(int n) {
	vector <int> t;
	for (int i = 1; i * i <= n; i++) 
		if (n % i == 0) {
			t.emplace_back(i);
			if (i * i != n) t.emplace_back(n / i);
		}
	return t;
}
int calc(int p) {
	vector <int> t = getdiv(p - 1);
	for (int i = 1; i <= p - 1; i++) {
		bool ok = 1;
		for (auto x : t) {
			if (x == p - 1) continue;
			if (ksm(i, x, p) == 1) {
				ok = false;
				break;
			} 
		}
		if (ok) return i;
	} 
	return 2;
}
int n, m, p, val[N], id[N], a[N], b[N], c[N];
LL ans;
void solve() {
	cin >> p;
	int g = calc(p);
	val[0] = 1;
	for (int i = 1; i <= p - 2; i++) val[i] = 1LL * val[i - 1] * g % p;
	for (int i = 0; i <= p - 2; i++) id[val[i]] = i;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1, greater <int> ());
	cin >> m;
	for (int i = 1; i <= m; i++) cin >> b[i], c[b[i]] = 1;
	if (a[n] == 0) {
		if (9 % p != 0 && c[13LL * inv(9, p) % p]) ans += n - 1;
		if (c[16 % p]) ans += n - 1;
		n--;
	}
//	cerr << "stage 1 : " << ans << "\n";
	poly A(p - 1), B(p - 1);
	for (int i = 1; i <= n; i++) A[id[a[i]]] = 1;
	for (int i = 1; i < p; i++) {
		if ((3 * i + 1) % p == 0 || (3 * i + 2) % p == 0) continue;
		int p1 = (1LL * (2 * i + 3) * (2 * i + 3) % p + 5LL * i * i % p) % p;
		int q1 = 1LL * (3 * i + 1) * (3 * i + 1) % p;
		int p2 = (1LL * (2 * i + 5) * (2 * i + 5) % p + 3) % p;
		int q2 = 1LL * (3 * i + 2) * (3 * i + 2) % p;
		int x = (1LL * p1 * inv(q1, p) % p + 1LL * p2 * inv(q2, p) % p) % p;
		if (c[x]) B[id[i]] = 1; 
	}
	poly C = NTT :: mul(A, B);
	for (int i = p - 1; i < (int)C.size(); i++) C[i - (p - 1)] += C[i];
	for (int i = 1; i <= n; i++) ans += C[id[a[i]]];
	cout << ans << "\n";
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
19
10
0 3 4 5 8 9 13 14 15 18
10
2 3 5 9 10 11 12 13 14 15
*/

B. Alexey the Sage of The Six Paths

构造一张二分图 \(G\),左右两边各有 \(n\) 个点,左边的点标号为 \(1∼n\),右边的点标号为 \(n+1∼2n\)。你需要连恰好 \(m\) 条边,允许有重边。

\(d_i\) 为点 \(i\) 的度数,即 \(G\) 中与 \(i\) 相连的边数。定义 \(f(G)\) 表示 \(G\) 的最大匹配。

给定 \(L,R\),你需要使得 \(f(G) \in [L,R]\),且 \(\sum\limits_{i=1}^n a_{i,d_i}\) 的值最小。

\(1 \le n,m \le 30\)


考虑怎么处理最大匹配。我们希望能把它变成可以两边分别计算的条件。

一个非常厉害的想法是,考虑同时钦定一组大小为 \(k\) 的匹配和一组大小为 \(k\) 的点覆盖,由于最大匹配等于最小点覆盖,此时一定有 \(f(G) = k\)

显然对于 \(k\) 条匹配边,一定是每条边两个端点中恰好有一个在点覆盖中,而不在匹配中的点一定都不在点覆盖中。分别考虑两边的点,假设我们已经确定了:

  • 共有 \(x_1\) 条匹配边,其中有 \(x_2\) 条左端点在点覆盖中。
  • 共有 \(x_3\) 条非匹配边,其中有 \(x_4\) 条左端点在点覆盖中。
  • 共有 \(y_1\) 条匹配边,其中有 \(y_2\) 条右端点在点覆盖中。
  • 共有 \(y_3\) 条非匹配边,其中有 \(y_4\) 条右端点在点覆盖中。

考虑需要满足哪些条件。最大匹配为 \(k\) 且每条匹配边恰好被一个点覆盖,那么有 \(x_1=y_1=k\)\(x_2+y_2 = k\)。而所有非匹配边也必须至少被一个点覆盖,则需要 \(x_3+y_3 \le \min(x_4+y_4, m-k)\)。只需要满足这些条件,就一定存在一个满足限制且 \(f(G) = k\)\(G\)

对于两边分别 DP,状态中记录下 \(x_1 \sim x_4\)\(y_1 \sim y_4\) 的值即可。

接下来考虑如何构造方案。首先可以记录 DP 转移点,那么根据 DP 数组我们可以得到在最优方案中对于每个点的这些信息:

  • 度数 \(d_i\)
  • 其是否是某条匹配边的端点。
  • 其是否在钦定的点覆盖中。

构造只需要满足最大匹配恰为 \(k\) 且每条边都能被覆盖。我们先拿出所有需要作为匹配边端点的点,造出 \(k\) 条匹配边,剩下只需要考虑点覆盖的条件。

通过计算度数我们可以得出有多少边需要被覆盖两次,造完这些边之后剩下所有边都恰好有一个端点在点覆盖中,从一边的匹配点向另一边的非匹配点依次连即可。总时间复杂度 \(\mathcal{O}(n^3m^3)\),瓶颈于在 DP 且常数极小。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
#define int long long
constexpr int N = 31, inf = 1e12;
bool Mbe;
int n, m, l, r, a[2][N][N], f[N][2][N][N][N][N];
pi g[N][2][N][N][N][N];
int key[2][N], mat[2][N], deg[2][N];
vector <pi> ns;
void chkmn(int &x, int y, pi &tx, pi ty) {
	if (x > y) {
		x = y;
		tx = ty;
	}
}
void getinfo(int i, int t, int x1, int x2, int x3, int x4) {
	if (i == 0) return;
	pi cur = g[i][t][x1][x2][x3][x4];
	int c = cur.se, d = cur.fi;
	deg[t][i] = d;
	if (c == 1) { // neither mat nor key
		getinfo(i - 1, t, x1, x2, x3 - d, x4);
	} else if (c == 2) { // mat but not key 
		mat[t][i] = 1;
		getinfo(i - 1, t, x1 - 1, x2, x3 - (d - 1), x4);
	} else { // mat and key 
		mat[t][i] = key[t][i] = 1;
		getinfo(i - 1, t, x1 - 1, x2 - 1, x3 - (d - 1), x4 - (d - 1));
	}
}
void link(int x, int y) {
	ns.emplace_back(x, y + n), deg[0][x]--, deg[1][y]--;
}
void construct() {
	vector <int> v[2][2];
	for (int j = 0; j < 2; j++)
		for (int i = 1; i <= n; i++) 
			if (mat[j][i]) {
				if (key[j][i]) v[j][1].push_back(i);
				else v[j][0].push_back(i);
			}
	assert(v[0][0].size() == v[1][1].size() && v[0][1].size() == v[1][0].size());
	for (int i = 0; i < 2; i++) {
		auto p = v[0][i], q = v[1][i ^ 1];
		for (int j = 0; j < (int)p.size(); j++) 
			link(p[j], q[j]);
	}
	int s0 = 0, s1 = 0;
	for (int i = 1; i <= n; i++) s0 += deg[0][i], s1 += deg[1][i];
	assert(s0 == s1);
	s0 = s1 = 0;
	for (int i = 1; i <= n; i++) {
		if (key[0][i]) s0 += deg[0][i];
		if (!key[1][i]) s1 += deg[1][i]; 
	}
	int rest = s0 - s1;
	for (int i = 0; i < 2; i++)
		for (int j = 0; j < 2; j++) {
			v[i][j].clear();
		}
	for (int j = 0; j < 2; j++)
		for (int i = 1; i <= n; i++) {
			if (!deg[j][i]) continue;
			if (key[j][i]) v[j][1].push_back(i);
			else v[j][0].push_back(i);
		}
	auto &p = v[0][1], &q = v[1][1];
	while (rest) {
		link(p.back(), q.back());
		if (!deg[0][p.back()]) p.pop_back();
		if (!deg[1][q.back()]) q.pop_back();
		rest--;
	}
	for (int i = 0; i < 2; i++) {
		auto &p = v[0][i], &q = v[1][i ^ 1];
		while (p.size()) {
			link(p.back(), q.back());
			if (!deg[0][p.back()]) p.pop_back();
			if (!deg[1][q.back()]) q.pop_back();
		}
	}
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m >> l >> r;
	for (int o = 0; o < 2; o++) 
		for (int i = 1; i <= n; i++)
			for (int j = 0; j <= m; j++)
				cin >> a[o][i][j];
	memset(f, 0x3f, sizeof(f));
	f[0][0][0][0][0][0] = f[0][1][0][0][0][0] = 0;
	for (int i = 1; i <= n; i++) {
		for (int t = 0; t < 2; t++) 
			for (int x1 = 0; x1 < i; x1++) 
				for (int x2 = 0; x2 <= x1; x2++)
					for (int x3 = 0; x3 <= m; x3++)
						for (int x4 = 0; x4 <= x3; x4++) {
							int val = f[i - 1][t][x1][x2][x3][x4];
							for (int cur = 0; cur <= m - x1 - x3; cur++) {
								chkmn(f[i][t][x1][x2][x3 + cur][x4], val + a[t][i][cur], g[i][t][x1][x2][x3 + cur][x4], make_pair(cur, 1));
								if (m - x1 - x3 - cur) {
									chkmn(f[i][t][x1 + 1][x2][x3 + cur][x4], val + a[t][i][cur + 1], g[i][t][x1 + 1][x2][x3 + cur][x4], make_pair(cur + 1, 2));
									chkmn(f[i][t][x1 + 1][x2 + 1][x3 + cur][x4 + cur], val + a[t][i][cur + 1], g[i][t][x1 + 1][x2 + 1][x3 + cur][x4 + cur], make_pair(cur + 1, 3)); 
								}	
							}
						}
	}
	int ans = inf;
	for (int x1 = l; x1 <= r; x1++) {
		for (int x2 = 0; x2 <= x1; x2++) 
			for (int x4 = 0; x4 <= m - x1; x4++)
				for (int y4 = m - x1 - x4; y4 <= m - x1; y4++)
					ans = min(ans, f[n][0][x1][x2][m - x1][x4] + f[n][1][x1][x1 - x2][m - x1][y4]);
	}
	if (ans >= inf) return cout << "DEFEAT\n", 0;
	for (int x1 = l; x1 <= r; x1++) {
		for (int x2 = 0; x2 <= x1; x2++) 
			for (int x4 = 0; x4 <= m - x1; x4++)
				for (int y4 = m - x1 - x4; y4 <= m - x1; y4++)
					if (ans == f[n][0][x1][x2][m - x1][x4] + f[n][1][x1][x1 - x2][m - x1][y4]) {
						getinfo(n, 0, x1, x2, m - x1, x4);
						getinfo(n, 1, x1, x1 - x2, m - x1, y4);
						goto end;
					}
	}
	end :
	construct();
	cout << ans << "\n";
	for (auto [x, y] : ns) cout << x << " " << y << "\n";
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
	return 0;
}
/*
3 5 2 3
100 75 125 150 175 200
125 100 75 100 125 150
225 200 175 200 225 250
225 200 175 200 225 250
125 100 75 100 125 150
100 75 125 150 175 200
*/

C. Steel Ball Run

给定一颗 \(n\) 个点的树。\(q\) 次操作,每次会在某个点上加或删一个球。

每次操作后求出使得所有球都在同一个节点的最小步数。

定义一步操作为:选择一个点 \(u\),将其上的一个球移动至 \(u\) 的某个邻居 \(v\)

\(1 \le n,q \le 10^5\)


嗨呀这不是我们幻想乡战略游戏的梗吗,下次转载记得标明出处。

设当前球数为 \(k\)。考虑一个利用点分治求答案的方法:如果当前 \(u\) 存在某个子树 \(v\) 满足 \(v\) 内的球数不小于 \(\frac{k}{2}\),那么重心一定在这个子树内。我们进入这个子树,然后把 \(v\) 子树外的所有球移到 \(v\)

多次询问考虑建点分树改成动态点分治。在 \(u\) 时我们需要找最大的子树,对于每个点维护子树球数的 set即可。然后把 \(v\) 子树外的所有球移进 \(v\) 这一步可以预处理点分树上的每个儿子在原树上对应哪个子树,然后在那个位置建一个新点更新对应信息。通过维护一些距离信息容易计算答案。

一个点更新的时候需要爬所有祖先更新子树信息。算距离时使用 ST 表 \(\mathcal{O}(1)\) 求 LCA,总时间复杂度为 \(\mathcal{O}(n\log n+q\log^2n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 2e5 + 5; 
bool Mbe;
int n, q, dfn[N], dep[N], tim; pi t[N][20];
vector <int> e[N];
template <typename T> struct pq {
	priority_queue <T> p, q;
	void ins(T x) {
		p.push(x);
	}
	void del(T x) {
		q.push(x);
	}
	T top() {
		while (p.size() && q.size() && p.top() == q.top()) p.pop(), q.pop();
		return p.empty() ? T() : p.top();
	}
};
void dfs(int u, int fa) {
	dfn[u] = ++tim;
	t[tim][0] = make_pair(dep[u] = dep[fa] + 1, fa);
	for (auto v : e[u])
		if (v != fa) dfs(v, u);
}
int lca(int x, int y) {
	if (x == y) return x;
	x = dfn[x];
	y = dfn[y];
	if (x > y) swap(x, y);
	int k = __lg(y - x);
	return min(t[x + 1][k], t[y - (1 << k) + 1][k]).se;
}
int dis(int x, int y) {
	int z = lca(x, y);
	return dep[x] + dep[y] - 2 * dep[z];
}
int siz[N], vis[N], fa[N], anc[N], root, cnt[N], tmp[N];
LL sum1[N], sum2[N];
pq <pi> f[N]; pi g[N];
int get_root(int u, int fa, int n) {
	int res = 0;
	bool ok = true;
	siz[u] = 1;
	for (auto v : e[u]) {
		if (vis[v] || v == fa) continue;
		res = max(res, get_root(v, u, n));
		siz[u] += siz[v];
		ok &= siz[v] * 2 <= n; 
	}
	if (ok && siz[u] * 2 >= n) res = max(res, u);
	return res;
} 
int build(int u, int n) {
	u = get_root(u, 0, n);
	get_root(u, 0, n);
	vis[u] = 1;
	for (auto v : e[u]) {
		if (vis[v]) continue;
		int it = build(v, siz[v]);
		fa[it] = u, anc[it] = v;
	}
	return u;
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		e[u].emplace_back(v);
		e[v].emplace_back(u);
	}	
	dfs(1, 0);
	for (int j = 1; j <= 19; j++)
		for (int i = 1; i + (1 << j) - 1 <= n; i++) {
			t[i][j] = min(t[i][j - 1], t[i + (1 << j - 1)][j - 1]);
		}
	root = build(1, n);
	for (int i = 1; i <= n; i++) if (fa[i]) f[fa[i]].ins(make_pair(0, i));
	cin >> q;
	while (q--) {
		char c;
		int p;
		cin >> c >> p;
		int w = c == '+' ? 1 : -1;
		for (int u = p; u; u = fa[u]) {
			if (fa[u]) {
				f[fa[u]].del(make_pair(cnt[u], u));
				f[fa[u]].ins(make_pair(cnt[u] + w, u));
				int val = dis(fa[u], p) * w;
				sum1[fa[u]] += val;
				sum2[u] += val;
			}
			cnt[u] += w;
		}
		int k = cnt[root], u = root;
		while (true) {
			pi cur = max(f[u].top(), g[u]);
			if (cur.fi * 2 <= k) break;
			int v = cur.se;
			int c = cnt[u] - cnt[v] + tmp[u] - tmp[v];
			u = v;
			for (int x = anc[u]; x; x = fa[x]) {
				tmp[x] += c;
				g[fa[x]] = max(g[fa[x]], make_pair(cnt[x] + tmp[x], x));
			}
		}
		LL ans = sum1[u];
		for (int x = u; fa[x]; x = fa[x]) {
			ans += sum1[fa[x]] - sum2[x] + 1LL * (cnt[fa[x]] - cnt[x]) * dis(fa[x], u);
			for (int j = anc[x]; j; j = fa[j]) tmp[j] = 0, g[j] = make_pair(0, 0);
		}
		cout << ans << "\n";
	}
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
6
1 2
2 3
3 4
4 5
2 6
5
+ 1
+ 4
+ 5
- 5
+ 6
*/

D. The Jump from Height of Self-importance to Height of IQ Level

给定一个长为 \(n\) 的序列 \(h_n\)。共 \(q\) 次操作,每次操作把一个区间插入到另一个位置。

每次操作后你需要判断是否存在 \(1 \le i < j < k \le n\),满足 \(h_i < h_j < h_k\)

\(1 \le n,q \le 120000\)


使用平衡树维护操作。假设我们要合并 \(u\) 的左儿子 \(L\) 与右儿子 \(R\),先不考虑节点 \(k\) 的贡献。

我们需要找到 \(L\) 中,所有长为 \(2\) 的上升子序列 \((x_1,x_2)\) 中,\(x_2\) 最小的那个,若右儿子 \(R\) 的最大值 \(M\) 大于 \(x_2\),则存在长为 \(3\) 的上升子序列 \((x_1,x_2,M)\)

对于 \(R\),我们同理考虑所有长为 \(2\) 的上升子序列 \((y_1,y_2)\) 中,\(y_1\) 最大的那个。

考虑在合并过程中怎么维护这些信息。以 \((x_1,x_2)\) 为例,即对于 \(L\) 中的最小值 \(m\),找 \(R\) 中大于 \(m\) 的最小值 \(t_m\),组成 \((m,t_m)\)

不妨假设此时 \(k\) 中不存在长为 \(3\) 的上升子序列。可以发现这是一个很强的限制,我们任选 \(R\) 中的两个位置 \(t_1,t_2\) 满足 \(p_{t_1} > m\)\(p_{t_2} > m\)\(t_1 < t_2\),那么一定有 \(p_{t_1} > p_{t_2}\),否则 \((m,p_{t_1},p_{t_2})\) 组成一个长为 \(3\) 的上升子序列。

因此,如果右儿子最大值大于 \(m\) 则往右递归,否则往左递归,这样一次的复杂度是 \(\mathcal{O}(\log n)\) 的。

接着将 \(u\) 的贡献考虑进去即可。总时间复杂度为 \(\mathcal{O}(n+q\log^2n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
constexpr int N = 2e5 + 5, inf = 1e9; 
bool Mbe;
int n, q;
mt19937 Rand(114514);
int tot, rnd[N], siz[N], lc[N], rc[N];
int val[N], mn[N], mx[N], hmn[N], hmx[N], f[N];
int create(int v) {
	int x = ++tot;
	mn[x] = mx[x] = val[x] = v;
	siz[x] = 1;
	hmn[x] = inf;
	hmx[x] = -inf;
	f[x] = 0;
	rnd[x] = Rand();
	return x;
}  
int find_mx(int x, int v) {
	if (mx[x] <= v) return -inf;
	if (siz[x] == 1) return val[x];
	if (mx[rc[x]] > v) return find_mx(rc[x], v);
	if (val[x] > v) return val[x];
	return find_mx(lc[x], v);
}
int find_mn(int x, int v) {
	if (mn[x] >= v) return inf;
	if (siz[x] == 1) return val[x]; 
	if (mn[lc[x]] < v) return find_mn(lc[x], v);
	if (val[x] < v) return val[x];
	return find_mn(rc[x], v);
}
void push_up(int x) {
	siz[x] = siz[lc[x]] + siz[rc[x]] + 1;
	mn[x] = min(val[x], min(mn[lc[x]], mn[rc[x]]));
	mx[x] = max(val[x], max(mx[lc[x]], mx[rc[x]]));
	
	f[x] = f[lc[x]] | f[rc[x]];
	f[x] |= mn[lc[x]] < hmx[rc[x]];
	f[x] |= hmn[lc[x]] < mx[rc[x]]; 
	
	f[x] |= val[x] < hmx[rc[x]];
	f[x] |= hmn[lc[x]] < val[x];
	f[x] |= mn[lc[x]] < val[x] && val[x] < mx[rc[x]]; 
	if (f[x]) return;
	
	hmn[x] = min(hmn[lc[x]], hmn[rc[x]]);
	hmx[x] = max(hmx[lc[x]], hmx[rc[x]]);
	
	int v = find_mx(rc[x], min(mn[lc[x]], val[x]));
	if (v > min(mn[lc[x]], val[x])) hmn[x] = min(hmn[x], v);
	
	v = find_mn(lc[x], max(mx[rc[x]], val[x]));
	if (v < max(mx[rc[x]], val[x])) hmx[x] = max(hmx[x], v);
	
	if (val[x] > mn[lc[x]]) hmn[x] = min(hmn[x], val[x]);
	if (val[x] < mx[rc[x]]) hmx[x] = max(hmx[x], val[x]);
} 
void split(int p, int k, int &x, int &y) {
	if (!p) return x = y = 0, void();
	if (siz[lc[p]] < k) {
		x = p, split(rc[p], k - siz[lc[p]] - 1, rc[p], y);
	} else {
		y = p, split(lc[p], k, x, lc[p]);
	}
	push_up(p);
}
int merge(int x, int y) {
	if (!x || !y) return x + y;
	int p;
	if (rnd[x] < rnd[y]) {
		p = x, rc[x] = merge(rc[x], y);
	} else {
		p = y, lc[y] = merge(x, lc[y]);
	}
	push_up(p);
	return p;
}
void flatten(int p, vector <int> &v) {
//	cout << p << " : val = " << val[p] << ", lc = " << lc[p] << ", rc = " << rc[p] << "\n";
	if (lc[p]) flatten(lc[p], v);
	v.emplace_back(val[p]);
	if (rc[p]) flatten(rc[p], v); 
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	int root = 0;
	mn[0] = inf;
	mx[0] = -inf; 
	hmn[0] = inf;
	hmx[0] = -inf;
	for (int i = 1; i <= n; i++) {
		int v;
		cin >> v;
		root = merge(root, create(v));
	}
	cin >> q;
	while (q--) {
		int l, r, k;
		cin >> l >> r >> k;
		if (k) { 
		int p = (r - k) - l + 1;
		assert(p + k == r - l + 1); 
		if (l == 1 && r == n) {
			int A, B;
			split(root, p, A, B);
			root = merge(B, A);
		} else if (l == 1) {
			int A, B, C;
			split(root, p, A, B);
			split(B, k, B, C);
			root = merge(B, merge(A, C));
		} else if (r == n) {
			int A, B, C;
			split(root, l - 1, A, B);
			split(B, p, B, C);
			root = merge(A, merge(C, B));	
		} else {
			int A, B, C, D;
			split(root, l - 1, A, B);
			split(B, p, B, C);
			split(C, k, C, D);
			root = merge(A, merge(C, merge(B, D)));
		}
		}
//		vector <int> v;
//		if (q == 0) cout << root << "\n", flatten(root, v);
//		for (auto x : v) cout << x << " ";
//		cout << "\n"; 
//		cout << hmx[1] << "\n";
		if (f[root]) cout << "YES\n";
		else cout << "NO\n";
	}
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
6
6 5 4 3 2 1
3
1 1 0
1 3 1
2 5 3
*/

E. Minimums on the Edges

给定一张 \(n\) 个点 \(m\) 条边的无向图,你需要在节点上放置恰好 \(s\) 个棋子。定义一条边的权值为两端点棋子数量的最小值,最大化所有边的权值和并构造方案。

\(1 \le n \le 18\)\(0 \le m \le 10^5\)\(0 \le s \le 100\)


一个比较 naive 的想法:设点 \(i\) 上最终棋子数量为 \(c_i\),考虑按 \(c_i\) 从大到小加点,那么新增边的权值就是 \(c_i\)。于是可以状压 DP,设 \(f_{S,j,k}\) 表示当前加了 \(S\) 内的点,总共用了 \(j\) 个棋子,当前 \(c_i = k\) 的最大权值,转移枚举新加的点满足 \(c_p \le k\),这样时间复杂度是 \(\mathcal{O}(n2^n \cdot s^3)\) 的,不太行。

考虑怎么把 \(k\) 这一维干掉。考虑拆贡献,由于 \(k = \sum\limits_{i = 1}[i \le k]\),那么如果已经确定了 \(c_i\),我们可以这样计算权值和:从大到小枚举 \(k\),找出所有 \(k \le c_p\) 的点 \(p\) 组成的集合 \(S_k\),把答案加上 \(e_S\)。其中 \(e_S\) 表示两端都在 \(S\) 内的边数,这可以用 FMT 在 \(\mathcal{O}(n2^n)\) 的时间内预处理。

这样就可以对着这个过程 DP 了,设 \(f_{S,j}\) 表示当前加了 \(S\) 内的点,总共用了 \(j\) 个棋子的最大权值,转移有两种:\(S\) 内所有点放一个棋子并获得 \(e_S\) 的权值,或者往 \(S\) 中新加入一个 \(c_p=0\) 的点。构造只需要记录 DP 转移点即可,时间复杂度 \(\mathcal{O}(n2^n \cdot s)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 18, M = 105;
bool Mbe;
int n, m, k, a[N];
LL f[1 << N][M], c[1 << N]; int g[1 << N][M];
void construct(int i, int j) {
	if (i == 0) 
		return;
	if (j == 1) {
		for (int p = 0; p < n; p++)
			if (i >> p & 1) {
				a[p]++;
				break; 
			}
		return;
	}
	if (g[i][j] == i) {
		for (int p = 0; p < n; p++) 
			if (i >> p & 1) 
				a[p]++;
		construct(g[i][j], j - __builtin_popcount(i));
	} else construct(g[i][j], j);
}
void solve() {
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		u--;
		v--;
		c[(1 << u) | (1 << v)]++;
	}
	for (int i = 0; i < n; i++)
		for (int j = 0; j < 1 << n; j++)
			if (j >> i & 1)
				c[j] += c[j ^ (1 << i)];
	for (int i = 0; i < 1 << n; i++)
		for (int j = 0; j <= k; j++) {
			if (j + __builtin_popcount(i) <= k) 
				if (f[i][j + __builtin_popcount(i)] < f[i][j] + c[i]) {
					f[i][j + __builtin_popcount(i)] = f[i][j] + c[i];
					g[i][j + __builtin_popcount(i)] = i;
				}
			for (int p = 0; p < n; p++)
				if (!(i >> p & 1))
					if (f[i | (1 << p)][j] < f[i][j]) {
						f[i | (1 << p)][j] = f[i][j];
						g[i | (1 << p)][j] = i;
					}
		}
//	cout << f[(1 << n) - 1][k] << "\n";	
	for (int i = 0; i < n; i++) g[1 << i][1] = 1 << i;
	construct((1 << n) - 1, k);
	for (int i = 0; i < n; i++) cout << a[i] << " ";
	cout << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
4 4 6
1 2
2 3
3 1
1 4
*/

F. IQ Test

你有一个集合 \(S\),初始只有 \(0,1,2\)。每步操作你可以选择 \(S\) 中两个数 \(x,y\),把 \(x^2-y\) 加进集合 \(S\)

请你构造方案在 \(43\) 步之内造出 \(n\)

\(1 \le n \le 10^{18}\)


考虑倒着做,最后要得到 \(n\),我们需要找一个 \(x,y\) 满足 \(x^2 - y = n\) 转化为造出 \(x,y\)

\(x = \lceil \sqrt{n} \rceil,y = x^2 - n\),注意到 \((x+1)^2 - x^2 = 2x+1\),那么 \(x,y\) 都是 \(\sqrt n\) 级别的,毛估估一下应该很能过。然后就过了。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define int long long
#define fi first
#define se second
constexpr int N = 1e6 + 5;
bool Mbe;
int n;
vector <pi> t;
map <int, bool> f;
void solve() {
	cin >> n;
	priority_queue <int> q;
	f[0] = f[1] = f[2] = 1;
	q.push(n);
	while (!q.empty()) {
		int u = q.top();
		q.pop();
		if (f[u]) continue;
		f[u] = 1;
		int v = ceil(sqrt(u));
		int x = v, y = x * x - u;
		q.push(x);
		q.push(y);
		t.emplace_back(x, y);
	}
	reverse(t.begin(), t.end());
	for (auto [x, y] : t) cout << x << " " << y << "\n"; 
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}

G. AtCoder Quality Problem

你有一个 \(n\) 个元素的集合 \(S\),你需要给 \(S\) 的所有子集染色。对于子集 \(S\),染红的代价为 \(r_S\),染蓝的代价为 \(b_S\)

染色需要满足,若子集 \(U,V\) 颜色相同,那么 \(U \cup V\) 的颜色与它们都相同。求最小代价。

\(0 \le n \le 20\)\(-10^9 \le r_i,b_i \le 10^9\)


预处理出 \(R(S) = \sum\limits_{T \subseteq S} r_T\)\(B(S) = \sum\limits_{T \subseteq S} b_T\),利用 FMT 可做到 \(\mathcal{O}(n2^n)\)

考虑 DP,设 \(f_S,g_S\) 分别表示考虑了 \(S\) 的所有子集,且 \(S\) 为红或蓝的最小代价。以 \(f_S\) 为例,注意到此时必然存在某个元素 \(e \in S\),满足所有 \(e \in T \subseteq S\) 的子集 \(T\) 均为红色。证明考虑反证,如果这样的元素不存在,那么 \(S\) 所有蓝色子集的并恰为 \(S\),那么 \(S\) 应是蓝色,矛盾。

于是可以枚举这个元素 \(e\) 进行转移,有 \(f_S = \min\limits_{e \in S} R(S) - R(S/e) + f(S/e)\),时间复杂度 \(\mathcal{O}(n2^n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
constexpr int N = 1 << 20;
bool Mbe;
int n, lim;
LL a[N], b[N], f[N];
void solve() {
	cin >> n;
	lim = 1 << n;
	for (int i = 0; i < lim; i++) cin >> a[i];
	for (int i = 0; i < lim; i++) cin >> b[i];
	for (int i = 1; i < lim; i <<= 1) 
		for (int j = 0; j < lim; j += i << 1) 
			for (int k = 0; k < i; k++) {
				a[i + j + k] += a[j + k];
				b[i + j + k] += b[j + k];
			}
	memset(f, 0x3f, sizeof(f));
	f[0] = min(a[0], b[0]);
	for (int s = 1; s < 1 << n; s++)
		for (int i = 0; i < n; i++)
			if (s >> i & 1) {
				int t = s ^ (1 << i);
				f[s] = min(f[s], f[t] + min(a[s] - a[t], b[s] - b[t]));
			} 
	cout << f[(1 << n) - 1] << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}

H. Mex on DAG

给定一张 \(n\) 个点 \(2n\) 条边的 DAG,边从 \(0 \sim 2n-1\) 编号。编号为 \(i\) 的边边权为 \(\lfloor \frac{i}{2} \rfloor\),求一条路径使得边权 \(\operatorname{mex}\) 值最小。你只需要求出这个最小值。

\(2 \le n \le 2000\)


考虑二分 \(\operatorname{mex}\),设二分的值为 \(m\),问题转化为判定是否存在一条路径,满足对于所有 \(0 \sim m\) 的边权,路径中至少包含一条边。注意到每种边权只有两条边,并且只选一条一定不劣。类似这种二选一问题可以考虑 2-SAT,总时间复杂度 \(\mathcal{O}(n^2 \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int N = 4e3 + 5;
bool Mbe;
int n, u[N], v[N], d[N];
vector <int> e[N];
bitset <N> t[N];
int low[N], dfn[N], tim, stk[N], tp, instk[N], col[N], bcnt;
void tarjan(int u) {
	dfn[u] = low[u] = ++tim;
	stk[++tp] = u, instk[u] = true;
	for (auto v : e[u]) {
		if (!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if (instk[v]) low[u] = min(low[u], dfn[v]);
	}
	if (low[u] == dfn[u]) {
		bcnt++;
		int x;
		do { 
			x = stk[tp--];
			instk[x] = false;
			col[x] = bcnt;
		} while (x != u);
	}
}
bool chk(int mid) {
	int m = mid * 2;
	for (int i = 0; i < m; i++) e[i].clear(), dfn[i] = 0;
	for (int i = 0; i < m; i++)
		for (int j = i + 1 + (i & 1 ^ 1); j < m; j++)
			if (!t[u[j]][v[i]] && !t[u[i]][v[j]]) {
				e[i].push_back(j ^ 1);
				e[j].push_back(i ^ 1);
			}
	tim = bcnt = 0;
	for (int i = 0; i < m; i++) if (!dfn[i]) tarjan(i);
	for (int i = 0; i < m; i += 2) if (col[i] == col[i ^ 1]) return false;
	return true;
}
void solve() {
	cin >> n;
	for (int i = 0; i < 2 * n; i++) {
		cin >> u[i] >> v[i];
		e[u[i]].emplace_back(v[i]);
		d[v[i]]++; 
	}
	queue <int> q;
	for (int i = 1; i <= n; i++) {
		if (!d[i]) q.push(i);
	}
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		t[u][u] = 1;
		for (auto v : e[u]) {
			t[v] |= t[u];
			if (--d[v] == 0) q.push(v); 
		}
	}
	int l = 1, r = n, ans;
	while (l <= r) {
		int m = l + r >> 1;
		if (chk(m)) ans = m, l = m + 1;
		else r = m - 1;
	} 
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
8
3 6
2 7
1 3
2 3
6 7
7 8
7 8
4 6
2 7
1 5
2 5
2 8
6 8
7 8
3 5
7 8
*/

I. Find the Vertex

给定一张 \(n\) 个点 \(m\) 条边的无向图,以及序列 \(d_1 \sim d_n\)。找一个点 \(s\) 满足 \(d_i \equiv dis(s,i) \pmod 3\)

\(1 \le n,m \le 5 \times 10^5\)


首先 \(s\) 一定满足 \(d_s=0\) 且对于所有邻居 \(t\)\(d_t = 1\)。然后可以注意到这样的点最多只有一个,且恰好就是 \(s\)。判断一下就行了,时间复杂度 \(\mathcal{O}(n+m)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
typedef tuple <int, int, int> ti3;
#define int long long
#define fi first
#define se second
constexpr int N = 5e5 + 5;
bool Mbe;
int n, m, c[N], d[N], vis[N];
vector <int> e[N];
mt19937 rnd(114514);
void chk(int s) {
	cout << s << "\n";
	for (int i = 1; i <= n; i++) d[i] = n + 1, vis[i] = 0;
	d[s] = 0;
	vis[s] = 1;
	queue <int> q;
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (auto v : e[u]) {
			if (d[v] > d[u] + 1) {
				d[v] = d[u] + 1;
				if (!vis[v]) vis[v] = 1, q.push(v);
			}
		}
	}
	bool ok = true;
	for (int i = 1; i <= n; i++) ok %= (d[i] % 3 == c[i]);
	if (ok) cout << s << "\n", exit(0);
} 
void solve() {
 	cin >> n >> m;
 	for (int i = 1; i <= n; i++) cin >> c[i];
 	for (int i = 1; i <= m; i++) {
 		int u, v;
 		cin >> u >> v;
 		e[u].emplace_back(v);
 		e[v].emplace_back(u);
	 }
	static int seq[N];
	iota(seq + 1, seq + n + 1, 1);
	for (int i = 1; i <= n; i++) swap(seq[i], seq[rnd() % i + 1]);
	for (int i = 1; i <= n; i++) {
		int u = seq[i];
		bool ok = c[u] == 0;
		for (auto v : e[u]) ok &= c[v] == 1;
		if (ok) chk(u);
	}
}
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1;
	while (t--) solve();
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}

J. Yet Another Mex Problem

给定长为 \(n\) 的序列 \(a\) 和整数 \(k\)。定义一个长为 \(m\) 的序列 \(b\) 的权值为 \(\operatorname{mex}(b_1 \sim b_m) \times \sum\limits_{i=1}^m b_i\)

你需要将 \(a\) 划分为若干长度不超过 \(k\) 的连续段,求最大权值和。

\(2 \le n \le 2 \times 10^5\)


首先考虑怎么搞 \(\operatorname{mex}\)。考虑从左往右扫右端点 \(j\),那么所有左端点 \(i\) 对应的 \(\operatorname{mex}\) 值单调不升,并且可以按照相同的 \(\operatorname{mex}\) 值划分成若干个段。

考虑 \(j - 1 \to j\) 的过程中这些段是怎样变化的。对于 \(\operatorname{mex} = a_j\) 的段,它会裂成一些 \(\operatorname{mex} > j\) 的段,注意到这样每次分裂长度都变短并且 \(\operatorname{mex}\) 变大,于是可以证明,本质不同的段只有 \(\mathcal{O}(n)\)

这一步可以使用线段树维护,具体来说令 \(t_i\) 表示 \(i\) 最后一次出现的位置,那么可以利用维护区间 \(\min\) 和线段树上二分,分别支持给定 \(c\) 查询 \(1 \sim j\) 中最后一个满足 \(\operatorname{mex} \geq c\) 的位置 \(p\),以及给定 \(i\) 查询 \(\operatorname{mex}(a_i \sim a_j)\)

接下来考虑怎么计算答案。考虑 DP,令 \(s_i\)\(a_1 \sim a_i\) 的和,则转移式为 \(f_i \gets f_j + s_i \times \operatorname{mex}(a_{j+1} \sim a_i) - s_j \times \operatorname{mex}(a_{j+1} \sim a_i)\)

枚举 \(\operatorname{mex}\) 相同的段,相当于要区间查询 \(f_j - s_j \times k\) 的最大值,可以线段树套李超线段树维护。

但是注意到,虽然本质不同的段只有 \(\mathcal{O}(n)\) 种,但是我们并不能每次都遍历所有段,否则遍历次数还是会退化到 \(\mathcal{O}(n^2)\)。考虑怎么快速处理一整个段的信息。注意到在上面的做法中,每个段的查询和 \(i\) 完全无关,因此对于整段,我们可以先算出对应 \(\operatorname{mex}\) 的 $ f_j - s_j \times k$ 的最大值 \(v\)。那么查询相当于求区间 \(s_i \times \operatorname{mex} + v\) 的最大值,同样可以用线段树套李超线段树维护。不是整段的部分最多只有一段,按照刚才的做法查询就行了。

但是你发现分裂段的时候需要删除,李超线段树好像不支持删除阿,那怎么办?注意到由于右端点移动的过程中 \(\operatorname{mex}\) 是单调不降的,因此实际上不删除也不会影响答案。总时间复杂度为 \(\mathcal{O}(n\log^2 n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <int, int> pi;
#define fi first
#define se second
constexpr int N = 2e5 + 5; 
bool Mbe;
int n, m, a[N], tot;
LL sum[N], f[N];
#define m ((l + r) >> 1)
struct SGT {
	int t[N << 2];
	void push_up(int x) { t[x] = min(t[x << 1], t[x << 1 | 1]); }
	void upd(int x, int l, int r, int p, int v) {
		if (l == r) return t[x] = v, void();
		if (p <= m) upd(x << 1, l, m, p, v);
		else upd(x << 1 | 1, m + 1, r, p, v);
		push_up(x);
	}
	int qmex(int x, int l, int r, int L) {
		if (l == r) return l;
		if (t[x << 1] < L) return qmex(x << 1, l, m, L);
		return qmex(x << 1 | 1, m + 1, r, L);
	}
	int qpos(int x, int l, int r, int R) {
		if (r < R) return t[x];
		if (l >= R) return N;
		return min(qpos(x << 1, l, m, R), 
		qpos(x << 1 | 1, m + 1, r, R));
	}
} sgt;
struct line {
	LL k, b; LL gety(LL x) { return k * x + b; }
	inline bool operator == (const line &p) { return k == p.k && b == p.b; }
};
struct node { int lc, rc; line ml; } t[N << 7];
inline line lmax(line x, line y, LL z) { return x.gety(z) > y.gety(z) ? x : y; }
struct SGT_lichao { 
	int root;
	inline int create() { t[++tot] = {-1, -1, {0, (LL)-1e18}}; return tot; }
	SGT_lichao() { root = create(); }
	void insert(int x, LL l, LL r, line li) {
		if (l == r) return t[x].ml = lmax(t[x].ml, li, l), void(0);
		if (lmax(t[x].ml, li, m) == li) swap(t[x].ml, li);
		if (lmax(t[x].ml, li, l) == li) {
			if (t[x].lc == -1) t[x].lc = create();
			insert(t[x].lc, l, m, li);
		} else if (lmax(t[x].ml, li, r) == li) {
			if (t[x].rc == -1) t[x].rc = create();
			insert(t[x].rc, m + 1, r, li);
		}
	}
	LL qry(int x, LL l, LL r, LL p) {
		if (x == -1) return -1e18;
		if (l == r) return t[x].ml.gety(p);
		if (p <= m) return max(qry(t[x].lc, l, m, p), 
			t[x].ml.gety(p));
		return max(qry(t[x].rc, m + 1, r, p), 
			t[x].ml.gety(p));
	}
}; 
struct SGT2 {
	SGT_lichao t[N << 2]; LL Le, Ri;
	void upd(int x, int l, int r, int p, line li) {
		t[x].insert(t[x].root, Le, Ri, li);
		if (l == r) return;
		if (p <= m) upd(x << 1, l, m, p, li);
		else upd(x << 1 | 1, m + 1, r, p, li);
	} 
	LL qry(int x, int l, int r, int L, int R, LL p) {
		if (L <= l && R >= r) return t[x].qry(t[x].root, Le, Ri, p);
		if (R < l || L > r) return -1e18;
		return max(qry(x << 1, l, m, L, R, p),
		qry(x << 1 | 1, m + 1, r, L, R, p));
	}
} sa, sb;
#undef m
bool Med;
signed main() {
//	fprintf(stderr, "%.9lfMb\n", 1.0 * (&Mbe - &Med) / 1048576);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i - 1] + a[i]; 
	sa.Le = 0, sa.Ri = n, sb.Le = 0, sb.Ri = sum[n];
	for (int i = 1; i <= n; i++) {
		int nl = min(sgt.qpos(1, 0, n, a[i] + 1), i),
		nr = min(sgt.qpos(1, 0, n, a[i]), i);
		sa.upd(1, 1, n, i, {-sum[i - 1], f[i - 1]}), sb.upd(1, 1, n, i, {0, f[i - 1]});
		sgt.upd(1, 0, n, a[i], i);
		while (nl < nr) {
			int mex = sgt.qmex(1, 0, n, nr),
			to = max(nl, sgt.qpos(1, 0, n, mex + 1));
			LL tmp = sa.qry(1, 1, n, to + 1, nr, mex);
			sb.upd(1, 1, n, to, {mex, tmp}), nr = to;
		}
		f[i] = sb.qry(1, 1, n, max(i - m, 0) + 1, i, sum[i]);
		nl = sgt.qmex(1, 0, n, max(i - m, 0) + 1);
		nr = min(sgt.qpos(1, 0, n, nl), i);
		f[i] = max(f[i], sum[i] * nl + sa.qry(1, 1, n, max(i - m, 0) + 1, nr, nl));
	} 
	cout << f[n] << "\n";
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n"; 
	return 0;
}
/*
10 5
0 2 0 1 2 1 0 2 2 1
*/
posted @ 2024-01-20 10:47  came11ia  阅读(25)  评论(0编辑  收藏  举报