首页 > 其他分享 >Petrozavodsk Summer 2019. Day 9. MEX Foundation Contest【杂题】

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

时间:2024-01-20 10:57:09浏览次数:24  
标签:Summer le return Foundation val Contest int long lc

比赛链接

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
*/

标签:Summer,le,return,Foundation,val,Contest,int,long,lc
From: https://www.cnblogs.com/came11ia/p/17961127

相关文章

  • 2020-2021 ICPC Southeastern European Regional Programming Contest (SEERC 2020)
    Preface最害怕的一集,徐神感冒身体不适只能口胡前半场,祁神中途也有事下机导致一段时间内只有我一个人在写题最后也是不负众望体现出没有队友我究竟是多么地彩笔,后面也索性开摆了直接后面3h梭哈写H题(主要写个假做法浪费很长时间)最后喜被卡常打完这场特意叫了一天休息,一是为了徐神......
  • Contest3376 - 2024寒假集训-排位赛竞赛(一)
    A:幂位和高精度。用高精度加法或乘法算出\(2^{1000}\),再将各位累加即为答案。#include<bits/stdc++.h>usingnamespacestd;#definecctieios::sync_with_stdio(0);cin.tie(0);cout.tie(0)stringAP_add(stringA,stringB)//高精度加法{intlena=A.size()......
  • AtCoder Beginner Contest 335
    A-2023(abc335A)题目大意给定一个字符串,将最后一位改成4。解题思路模拟即可。神奇的代码#include<bits/stdc++.h>usingnamespacestd;usingLL=longlong;intmain(void){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);strings;......
  • D - Summer Vacation
    D-SummerVacation题意:有n个任务,每一天可以选择一个未完成的任务i来完成,并在ai天后获得bi的利益。问在m天内最多可以获得多少利益。首先我们可以考虑假如i,j两个任务都在我们最后的答案里面,那么让a比较大的排在前面一定是更好的。所以我们先按照a降序排序。然后用一个set维护......
  • The 2021 Sichuan Provincial Collegiate Programming Contest
    题目链接:The2021SichuanProvincialCollegiateProgrammingContestA.Chuanpai题意:定义每一张川牌包含两个数字x,y,并且1<=x<= y<=6,求牌面上数字之和为n的牌种类解题思路:签到,预处理枚举即可查看代码map<int,int>mp;voidinit(){ for(inti=1;i<=6;i......
  • 2021 ICPC Southeastern Europe Regional Contest
    Preface这场打的挺好,感觉在题目难度不小的情况下能写出10题还是挺猛的可惜最后时间差一点不然甚至能再写出来一个EA.KingofStringComparison签到,本来徐神跟我说写个二分+Hash,然后我库库上去一顿写刚抄完板子就被赶下来了直接从后往前扫,记录距离当前最近的不同的位置出现......
  • AtCoder Beginner Contest 336
    题目链接:AtCoderBeginnerContest336A-LongLoong题意:输出Long,其中'o'的数量等于n解题思路:签到(其实没看清楚题目wa了一发)查看代码voidsolve(){ intn; cin>>n; cout<<'L'; while(n--)cout<<'o'; cout<<"ng";}......
  • 2020-2021 ACM-ICPC Latin American Regional Programming Contest J. Job Allocator
    Preface今天因为下午被强行拉回老家了,而且没带电脑回去然后就变成了徐神和祁神两个人写,我拿个手机在后面口胡了3h最后变成了在缺我一个人的前提下还能4h过10题的情况,感觉就算我在的话最多就是快点过H然后把剩下的时间拿去写个J这场因为没啥参与就不写整场的博客了,把赛后写的这......
  • AtCoder Grand Contest 046 F Forbidden Tournament
    洛谷传送门AtCoder传送门太厉害了!!!!!!首先竞赛图有个性质,若存在环则一定存在三元环。先把DAG的情况(一条链)特判了。然后缩点。发现非链底的部分不能存在大小\(>1\)的SCC。所以枚举非链底的部分有多少点,转化为SCC的情况。发现对于任意点(设为\(1\)号点),它的前驱连成一条链......
  • Atcoder Beginner Contest 330 题解
    AtCoderBeginnerContest330题解A-CountingPasses签到voidShowball(){intn,l;cin>>n>>l;intcnt=0;for(inti=0;i<n;i++){intx;cin>>x;cnt+=(x>=l);}cout<<cnt<<endl;}B-Minimize......