「MCOI-02」MCOI Round 2 题解

这场134过于板+水,但是2还是有质量的。所以讲题的顺序也按1342

「MCOI-02」Convex Hull 凸包

的确签到,按莫比乌斯反演的套路爆推式子就行了。不妨设 (nle m)

(ans=sum_{k=1}^{n}d(k)sum_{l|k}mu(l)sum_{k|i}d(i)sum_{k|j}d(j)=sum_{T=1}^{n}sum_{i=1}^{[n/T]}d(i)sum_{j=1}^{[m/T]}d(j)sum_{k|T}d(k)mu(frac{T}{k}))。然后后面那个东西线性筛+埃氏筛一下就好了。其实后面那玩意衡等于 (1) 我却没有发现。。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2000010;
template <typename T> void read(T &x) {
	T f = 1;
	char ch = getchar();
	for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
	for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	x *= f;
}
ll prime[MAXN], mu[MAXN], d[MAXN], mi[MAXN], g[2][MAXN], h[MAXN], cnt;
bool mark[MAXN]; 
ll n, m, p, ans;
void sieve() {
	mu[1] = d[1] = 1;
	for (int i = 2; i <= m; i++) {
		if (!mark[i]) {
			prime[++cnt] = i;
			mu[i] = -1;
			d[i] = 2;
			mi[i] = 1;
		}
		for (int j = 1; j <= cnt && prime[j] * i <= m; j++) {
			mark[i * prime[j]] = 1;
			if (i % prime[j] == 0) {
				d[i * prime[j]] = d[i] / (mi[i] + 1) * (mi[i] + 2);
				mi[i * prime[j]] = mi[i] + 1;
				break;
			}
			mu[i * prime[j]] = -mu[i];
			d[i * prime[j]] = d[i] << 1;
			mi[i * prime[j]] = 1;
		}
	}
	for (int i = 1; i <= m; i++) {
		for (int j = i; j <= m; j += i) {
			if (i <= n && j <= n) {
				g[0][i] += d[j];
			}
			g[1][i] += d[j];
		}
	}
	for (int i = 1; i <= m; i++) {
		for (int j = i; j <= m; j += i) {
			h[j] += d[i] * mu[j / i];
			h[j] %= p;
		}
	}
}
int main() {
	read(n); read(m); read(p);
	if (n > m) swap(n, m);
	sieve();
	for (int i = 1; i <= n; i++) {
		ans = (ans + g[0][i] * g[1][i] % p * h[i] % p + p) % p;
	}
	printf("%lld", ans);
	return 0;
}

「MCOI-02」Ancestor 先辈

容易发现其实就是判断一个区间是不是单调上升的,只需线段树维护即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 1000010;
const ll inf = 0x7f7f7f7f7f7f7f7f;
template <typename T> void read(T &x) {
	T f = 1;
	char ch = getchar();
	for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
	for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	x *= f;
}
struct node{
	bool mark;
	ll mi, mx, tag;
}tr[MAXN << 2];
ll n, m;
ll a[MAXN];
void add(ll x, ll v) {
	tr[x].mi += v;
	tr[x].mx += v;
	tr[x].tag += v;
}
void push_down(ll x) {
	if (tr[x].tag) {
		add(x << 1, tr[x].tag);
		add(x << 1 | 1, tr[x].tag);
		tr[x].tag = 0;
	}
}
void push_up(ll x) {
	tr[x].mark = (tr[x << 1].mark && tr[x << 1 | 1].mark && tr[x << 1].mx <= tr[x << 1 | 1].mi);
	tr[x].mi = min(tr[x << 1].mi, tr[x << 1 | 1].mi);
	tr[x].mx = max(tr[x << 1].mx, tr[x << 1 | 1].mx);
}
void build(ll x, ll l, ll r) {
	if (l == r) {
		tr[x].mi = tr[x].mx = a[l];
		tr[x].mark = true;
		return;
	}
	ll mid = (l + r) >> 1;
	build(x << 1, l, mid);
	build(x << 1 | 1, mid + 1, r);
	push_up(x);
}
void change(ll x, ll l, ll r, ll L, ll R, ll v) {
	if (l > R || r < L) return;
	if (L <= l && r <= R) {
		add(x, v);
		return;
	}
	ll mid = (l + r) >> 1;
	push_down(x);
	change(x << 1, l, mid, L, R, v);
	change(x << 1 | 1, mid + 1, r, L, R, v);
	push_up(x);
}
bool ans;
pair<ll, ll> ask(ll x, ll l, ll r, ll L, ll R) {
	if (!ans) return make_pair(0, 0);
	if (l > R || r < L) {
		return make_pair(inf, -inf);
	}
	if (L <= l && r <= R) {
		if (!tr[x].mark) ans = false;
		return make_pair(tr[x].mi, tr[x].mx);
	}
	ll mid = (l + r) >> 1;
	push_down(x);
	pair<ll, ll> p = ask(x << 1, l, mid, L, R), q = ask(x << 1 | 1, mid + 1, r, L, R);
	if (p.second > q.first) ans = false;
	return make_pair(p.first, q.second);
}
int main() {
	read(n); read(m);
	for (ll i = 1; i <= n; i++) read(a[i]);
	build(1, 1, n);
	for (ll i = 1; i <= m; i++) {
		ll opt, l, r, x;
		read(opt); read(l); read(r);
		if (opt == 1) {
			read(x);
			change(1, 1, n, l, r, x);
		} else {
			ans = true;
			pair<ll, ll> tmp = ask(1, 1, n, l, r);
			if (ans) puts("Yes");
			else puts("No");
		}
	}
	return 0;
}

「MCOI-02」Glass 玻璃

看到这种有线性结构的式子考虑枚举每条边的贡献。考虑以每个点为根,计算与其相连的边的贡献。我们把一条边拆成两部分计算答案,每次计算只用计算子树内的点权给的贡献,显然就是 (sum_{vin subtree(u)}sz[v]*V[v])。然后我们考虑每个点作为根的情况,直接换根dp即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 2000010;
const ll mod = 998244353;
template <typename T> void read(T &x) {
	T f = 1;
	char ch = getchar();
	for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
	for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	x *= f;
}
struct node{
	ll pre, to;
	ll len;
}edge[MAXN << 1];
ll head[MAXN], tot;
ll n;
ll val[MAXN];
ll sz[MAXN], dp[MAXN];
ll ans;
void add(ll u, ll v, ll l) {
	edge[++tot] = node{head[u], v, l};
	head[u] = tot;
}
void dfs1(ll x, ll fa) {
	sz[x] = 1;
	for (ll i = head[x]; i; i = edge[i].pre) {
		ll y = edge[i].to;
		if (y == fa) continue;
		dfs1(y, x);
		sz[x] += sz[y];
		dp[x] = (dp[x] + dp[y]) % mod;
	}
	dp[x] = (dp[x] + sz[x] * val[x]) % mod;
}
void get_ans(ll x) {
	for (ll i = head[x]; i; i = edge[i].pre) {
		ll y = edge[i].to;
		ans = (ans + dp[y] * (n - sz[y]) % mod * edge[i].len % mod + mod) % mod;
	}
}
void change_root(ll x, ll y) {
	//cut
	dp[x] = (dp[x] - dp[y] + mod) % mod;
	dp[x] = (dp[x] - sz[x] * val[x] + mod) % mod;
	sz[x] -= sz[y];
	dp[x] = (dp[x] + sz[x] * val[x] + mod) % mod;
	//link
	dp[y] = (dp[y] + dp[x] + mod) % mod;
	dp[y] = (dp[y] - sz[y] * val[y] + mod) % mod;
	sz[y] += sz[x];
	dp[y] = (dp[y] + sz[y] * val[y]) % mod;
}
void dfs2(ll x, ll fa) {
	get_ans(x);
	for (ll i = head[x]; i; i = edge[i].pre) {
		ll y = edge[i].to;
		if (y == fa) continue;
		change_root(x, y);
		dfs2(y, x);
		change_root(y, x);
	}
}
int main() {
	read(n);
	for (ll i = 1; i <= n; i++) read(val[i]);
	for (ll i = 1, u, v, l; i < n; i++) {
		read(u); read(v); read(l);
		add(u, v, l);
		add(v, u, l);
	}
	dfs1(1, 0);
	dfs2(1, 0);
	printf("%lld
", (ans << 1) % mod);
	return 0;
}

「MCOI-02」Build Battle

首先容易得到一个dp,设 (f_i) 代表匹配到颜色序列的第 (i) 位的方案数,我们只用考虑前 (m) 个位置,设 (s_i)(f_i) 的前缀和,有 (s_i=2*s_{i-1}-s_{i-m-1})

然后有一个经典trick,我们尝试递归递推式,从 (0) 走到 (n),一开始有一个值 (v=1),有两种走法,一种走到 (i+1),令 (v=v*2),还有一种走到 (i+m+1),令 (v=-v)。然后对于每条到 (n) 的路径,我们记录它的权值和。于是我们发现答案之和行动方式有关,这就变成了一个计数问题。

[ans=sum_{i=0}^{lfloorfrac{n}{m+1} floor}2^{n-(m+1)*i}*(-1)^i{n-(m+1)*i+ichoose i} ]

然后就可以 (O(nln n)) 预处理了。

#include <iostream>
#include <cstdio>

using namespace std;

namespace StandardIO{
	template <typename T> void read(T &x) {
		T f = 1;
		char ch = getchar();
		for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
		for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
		x *= f;
	}
	template <typename T> void write(T x) {
		if (x > 9) write(x / 10);
		putchar(x % 10 + '0');
	}
	template <typename T> void print(T x) {
		if (x < 0) putchar('-'), x = -x;
		write(x);
		putchar('
');
	}
}

namespace Solve{
	const int mod = 1e9 + 7;
	const int MAXN = 2e6 + 10;
	const int MAXQ = 1e6 + 10; 
	static int n, q, m;
	static long long ans[MAXN];
	static long long fac[MAXN], ifac[MAXN], inv[MAXN];
	
	long long ksm(long long x, long long y) {
		long long ret = 1;
		while (y) {
			if (y & 1) ret = (ret * x) % mod;
			x = (x * x) % mod;
			y >>= 1;
		}
		return ret;
	}
	
	void init() {
		fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
		inv[1] = 1;
		for (int i = 2; i <= n; i++) {
			inv[i] = (mod - mod / i) * inv[mod % i] % mod;
			fac[i] = fac[i - 1] * i % mod;
			ifac[i] = ifac[i - 1] * inv[i] % mod;
		}
	}
	
	long long C(long a, long b) {
		if (a < 0 || b < 0 || a < b) return 0;
		return fac[a] * ifac[a - b] % mod * ifac[b] % mod;
	}
	
	void MAIN() {
		StandardIO :: read(n); StandardIO :: read(q);
		init();
		while (q--) {
			StandardIO :: read(m);
			if (ans[m]) {
				StandardIO :: print(ans[m]);
			} else {
				int i = m;
				for (int j = 0; j <= n / (i + 1); j++) {
					ans[i] = (ans[i] + ksm(2, n - (i + 1) * j) * ksm(-1, j) * C(n - i * j, j) % mod + mod) % mod;
				}
				StandardIO :: print(ans[m]);
			}
		}
	}
}

int main() {
	Solve :: MAIN();
	return 0;
}