Loading

NOIP2024 简要题解

应 lgj 的要求。

T1 编辑字符串(edit)

考虑求出 \(x\) 表示两个串最多能匹配多少对 \(0\)

设两个串 \(0\) 的个数加起来为 \(s\),那么会发现恰好有 \(s - 2x\) 个位置是不匹配的,我们只需要最小化 \(s - 2x\) 即最大化 \(x\) 即可。

可以直接贪心求解,枚举每个位置判断是否能匹配一对 \(0\),处理出两个串的分段情况即可,时间复杂度 \(\mathcal O(Tn)\)

点击查看代码
#include <bits/stdc++.h>

namespace Initial {
	#define ll int
	#define ull unsigned long long
	#define fi first
	#define se second
	#define mkp make_pair
	#define pir pair <ll, ll>
	#define pb emplace_back
	#define i128 __int128
	using namespace std;
	const ll maxn = 1e5 + 10, mod = 1e9 + 7;
	const long long inf = 1e18;
	ll power(ll a, ll b = mod - 2) {
		ll s = 1;
		while(b) {
			if(b & 1) s = 1ll * s * a %mod;
			a = 1ll * a * a %mod, b >>= 1;
		} return s;
	}
	template <class T>
	const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void chkmax(T &x, const T y) { x = x < y? y : x; }
	template <class T>
	void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;

namespace Read {
	#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
	template <class T>
	void rd(T &x) {
		char ch; bool f = 0;
		while(!isdigit(ch = getchar()));
			if(ch == '-') f = 1;
		x = ch - '0';
		while(isdigit(ch = getchar()))
			x = (x << 1) + (x << 3) + ch - '0';
		if(f) x = -x;
	}
} using Read::rd;

ll t, n, cnta[maxn][2], cntb[maxn][2], sta[maxn], stb[maxn];
char a[maxn], b[maxn], p[maxn], q[maxn];

void solve() {
	rd(n), scanf("%s%s%s%s", a + 1, b + 1, p + 1, q + 1);
	memset(cnta, 0, sizeof cnta), memset(cntb, 0, sizeof cntb);
	for(ll i = 1; i <= n; i++) {
		++cnta[i][a[i] - '0'], sta[i] = i;
		if(p[i] == '1') {
			ll j = i;
			while(j < n && p[j + 1] == '1')
				++cnta[i][a[++j] - '0'], sta[j] = i;
			i = j;
		}
	}
	for(ll i = 1; i <= n; i++) {
		++cntb[i][b[i] - '0'], stb[i] = i;
		if(q[i] == '1') {
			ll j = i;
			while(j < n && q[j + 1] == '1')
				++cntb[i][b[++j] - '0'], stb[j] = i;
			i = j;
		}
	} ll ans = 0;
	for(ll i = 1; i <= n; i++) {
		ll x = sta[i], y = stb[i];
		if(cnta[x][0] && cntb[y][0])
			++ans, --cnta[x][0], --cntb[y][0];
		else if(cnta[x][1] && cntb[y][1])
			++ans, --cnta[x][1], --cntb[y][1];
	} printf("%d\n", ans);
}

int main() {
	rd(t); while(t--) solve();
	return 0;
}

T2 遗失的赋值(assign)

先对二元组 \((c_i, d_i)\) 按第一关键字排序,如果存在 \(i,j\) 满足 \(c_i = c_j\)\(d_i \not = d_j\) 则无解,直接输出 \(0\)

假设现在 \(c_{1\dots m}\) 是有序的,这 \(m\) 个位置把原来 \(n\) 个位置分成了 \(m + 1\) 段,每一段应是独立的,分开考虑。

  • 对于位置 \(j = 1\dots c_1 - 1\),由于 \(v\ge 2\),所以每个变量 \(x_j\) 只要不取 \(a_j\) 就一定合法,所以 \((a_j, b_j)\) 任取即可,方案数为 \(v^{2(c_1 - 1)}\)

  • 对于位置 \(j = c_m\dots n - 1\),发现更随意了,因为后面任填都合法,方案数为 \(v^{2(n - c_m)}\)

  • 对于 \(1\le i \le m - 1\) 每一段段 \([c_i, c_{i + 1} - 1]\),考虑容斥:总方案数显然为 \(v^{2(c_{i + 1} - c_i)}\),一种方案不合法当且仅当 \(a_{c_i} = d_i\)\(\forall j\in [c_i, c_{i + 1} - 2]\) 满足 \(b_j = a_{j + 1}\)\(b_{c_{i + 1} - 1} = d_{i + 1}\),因为如果中间存在某个 \(j\) 满足 \(b_{j - 1} \not = a_j\),对应的 \(x_{j + 1\dots c_{i + 1} - 1}\) 选择不取 \(a_{j + 1\dots c_{i + 1} - 1}\) 即可,方案数为 \((v - 1)v^{c_{i + 1} - c_i - 1}\),减掉之后就是 \(v^{2(c_{i + 1} - c_i)} - (v - 1) v^{c_{i + 1} - c_i - 1}\)

将所有方案数乘起来即可,时间复杂度 \(\mathcal O(Tm(\log m + \log n))\)

点击查看代码
#include <bits/stdc++.h>

namespace Initial {
	#define ll long long
	#define ull unsigned long long
	#define fi first
	#define se second
	#define mkp make_pair
	#define pir pair <ll, ll>
	#define pb emplace_back
	#define i128 __int128
	using namespace std;
	const ll maxn = 1e5 + 10, mod = 1e9 + 7;
	const long long inf = 1e18;
	ll power(ll a, ll b = mod - 2) {
		ll s = 1;
		while(b) {
			if(b & 1) s = 1ll * s * a %mod;
			a = 1ll * a * a %mod, b >>= 1;
		} return s;
	}
	template <class T>
	const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void chkmax(T &x, const T y) { x = x < y? y : x; }
	template <class T>
	void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;

namespace Read {
	#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, (1 << 22) - 10, stdin), p1 == p2)? EOF : *p1++)
	template <class T>
	void rd(T &x) {
		char ch; bool f = 0;
		while(!isdigit(ch = getchar()));
			if(ch == '-') f = 1;
		x = ch - '0';
		while(isdigit(ch = getchar()))
			x = (x << 1) + (x << 3) + ch - '0';
		if(f) x = -x;
	}
} using Read::rd;

ll t, n, m, v; pir a[maxn];

void solve() {
	rd(n), rd(m), rd(v);
	for(ll i = 1; i <= m; i++) rd(a[i].fi), rd(a[i].se);
	sort(a + 1, a + 1 + m);
	for(ll i = 1; i < m; i++)
		if(a[i].fi == a[i + 1].fi && a[i].se != a[i + 1].se)
			return puts("0"), void();
	ll ans = power(v, 2 * (a[1].fi - 1 + n - a[m].fi));
	for(ll i = 1; i < m; i++) {
		if(a[i].fi == a[i + 1].fi) continue;
		ans = ans * (power(v, 2 * (a[i + 1].fi - a[i].fi))
		 + mod - power(v, a[i + 1].fi - a[i].fi - 1) * (v - 1) %mod) %mod;
	} printf("%lld\n", ans);
}

int main() {
	rd(t); while(t--) solve();
	return 0;
}

T3 树的遍历(traverse)

  • \(k = 1\)

当从一条边 \(e_1\) 遍历到一个点 \(u\) 时,设 \(u\) 相连的边为 \(e_1,e_2,\dots, e_m\),那么:

  • 在新树上,\(e_1,e_2,\dots e_m\) 应连成一条链,其中 \(e_1\) 是链头。

  • 方案数显然为 \((m - 1)!\)\((\deg_u - 1) !\)

进而可知,总答案为 \(\prod_{u = 1} ^ n (\deg_u - 1) !\)

  • \(k = 2\)

一个非常显然的想法是用 \(2\prod_{u = 1} ^ n (\deg_u - 1) !\) 减去两条边开始遍历都能造出的新树的方案数。

根据 \(k = 1\) 的条件,以某条边为根开始遍历,对于所有点 \(u\) 及其连边 \(e_1, e_2, \dots, e_m\),设遍历到点 \(u\) 的边为 \(e_1\),都应该满足 \(e_1\) 是链头。

那么对于一个夹在这两条初始边中的任意一个点 \(u\) 及其连边 \(e_1, e_2, \dots, e_m\),设 \(e_1, e_2\) 分别为从两边遍历到 \(u\) 的边,应该满足 \(e_1, e_2\) 分别是这条链的链头和链尾,方案数为 \((\deg_u - 2)!\)

  • \(k < n\)

延续 \(k = 2\) 的条件,我们枚举初始边集 \(S\),计算方案数并乘上 \((-1)^{|S| - 1}\)

发现若存在三条边不在同一条路径上,设 \(u\) 为连向他们的中心点,则需要满足 \(e_1, e_2, e_3\) 三条边都要在链头或链尾,这显然不可能。

所以有贡献的边集只可能在一条路径上。进一步的,若 \(|S| \ge 3\),除了头和尾两条初始边,中间的初始边可有可无,不影响贡献,容斥系数抵消。

所以真正有贡献的只可能是 \(|S| = 1\)\(|S| = 2\)。其中 \(|S| = 1\) 是容易的,\(|S| = 2\) 可以简单树形 DP 求出。

点击查看代码
//#pragma GCC optimize(1)
//#pragma GCC optimize(2)
//#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
namespace Initial {
	#define ll long long
	#define ull unsigned long long
	#define fi first
	#define se second
	#define mkp make_pair
	#define pir pair <ll, ll>
	#define pb emplace_back
	#define i128 __int128
	using namespace std;
	template <class T>
	void rd(T &x) {
		char ch; bool f = 0;
		while(!isdigit(ch = getchar()))
			if(ch == '-') f = 1;
		x = ch - '0';
		while(isdigit(ch = getchar()))
			x = (x << 1) + (x << 3) + ch - '0';
		if(f) x = -x;
	}
	void write(const ll x, const char str[] = "") {
		if(x > 9) write(x / 10);
		putchar(x % 10 + '0'), printf("%s", str);
	}
	const ll maxn = 1e6 + 10, inf = 1e18, mod = 1e9 + 7;
	ll power(ll a, ll b = mod - 2) {
		ll s = 1;
		while(b) {
			if(b & 1) s = 1ll * s * a %mod;
			a = 1ll * a * a %mod, b >>= 1;
		} return s;
	}
	template <class T>
	const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void chkmax(T &x, const T y) { x = x < y? y : x; }
	template <class T>
	void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;

ll t, n, k, ans, f[maxn], key[maxn], deg[maxn], inv[maxn];
vector <pir> to[maxn]; ll fac[maxn];

void dfs(ll u, ll pk = 0, ll fa = 0) {
	for(pir e: to[u]) {
		ll v = e.fi, id = e.se;
		if(v == fa) continue;
		dfs(v, key[id], u);
		ans = (ans + mod - f[u] * f[v] %mod) %mod;
		f[u] = (f[u] + f[v] * inv[deg[u] - 1]) %mod;
	} if(pk) ans = (ans - f[u] + mod) %mod, f[u] = 1;
}

void solve() {
	rd(n), rd(k);
	for(ll i = 1; i <= n; i++)
		f[i] = key[i] = deg[i] = 0, to[i].clear();
	for(ll i = 1; i < n; i++) {
		ll u, v; rd(u), rd(v);
		++deg[u], ++deg[v];
		to[u].pb(mkp(v, i)), to[v].pb(mkp(u, i));
	} inv[1] = 1;
	for(ll i = 1, x; i <= k; i++)
		rd(x), key[x] = 1;
	for(ll i = 2; i <= n; i++)
		inv[i] = (mod - mod / i) * inv[mod % i] %mod;
	fac[0] = 1;
	for(ll i = 1; i <= n; i++) fac[i] = fac[i - 1] * i %mod;
	ans = k, dfs(1);
	for(ll i = 1; i <= n; i++) ans = ans * fac[deg[i] - 1] %mod;
	printf("%lld\n", ans);
}

int main() {
	rd(t), rd(t); while(t--) solve();
	return 0;
}

T4 树上查询(query)

考虑树上启发式合并维护所有连续段,不难发现我们可以造出 \(\mathcal O(n\log n)\) 个连续段。

如果观察得比较仔细,对于每个 \(i\)\((i, i + 1)\) 只会在一个点合并一次,每次合并至多产生一个连续段,所以本质不同的连续段个数为 \(\mathcal O(n)\),可以使用并查集。

考虑我们现在有 \(\mathcal O(n)\) 个区间以及权值 \((l_i, r_i, w_i)\),有若干个询问区间 \([l_q, r_q]\),求 \(\max\limits_{[l_i, r_i] \cap [l_q, r_q] \cap \mathbf N \ge k_q} w_i\)

分成四类:

  • \(l_i \le l_q \le r_i \le r_q\)

  • \(l_q \le l_i \le r_q \le r_i\)

  • \(l_i \le l_q \le r_q \le r_i\)

  • \(l_q \le l_i \le r_i \le r_q\)

第三类直接二维数点,是容易的。

第一类和第四类可以考虑这样做:按 \(r_i - l_i + 1\)\(k_q\) 排序,依次插入。插入 \((l_i, r_i, w_i)\) 时在位置 \(l_i\) chkmax \(w_i\),查询 \([l_q, r_q]\) 时求位置 \(l_q \sim r_q - k_q + 1\) 上的最大值。

第二类同理,不算并查集的话时间复杂度为 \(\mathcal O(n\log n)\)

点击查看代码
//#pragma GCC optimize(1)
//#pragma GCC optimize(2)
//#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
namespace Initial {
	#define ll long long
	#define ull unsigned long long
	#define fi first
	#define se second
	#define mkp make_pair
	#define pir pair <ll, ll>
	#define pb emplace_back
	#define i128 __int128
	using namespace std;
	template <class T>
	void rd(T &x) {
		char ch; bool f = 0;
		while(!isdigit(ch = getchar()))
			if(ch == '-') f = 1;
		x = ch - '0';
		while(isdigit(ch = getchar()))
			x = (x << 1) + (x << 3) + ch - '0';
		if(f) x = -x;
	}
	void write(const ll x, const char str[] = "") {
		if(x > 9) write(x / 10);
		putchar(x % 10 + '0'), printf("%s", str);
	}
	const ll maxn = 5e5 + 10, inf = 1e18, mod = 1e9 + 7;
	ll power(ll a, ll b = mod - 2) {
		ll s = 1;
		while(b) {
			if(b & 1) s = 1ll * s * a %mod;
			a = 1ll * a * a %mod, b >>= 1;
		} return s;
	}
	template <class T>
	const ll pls(const T x, const T y) { return x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void add(T &x, const T y) { x = x + y >= mod? x + y - mod : x + y; }
	template <class T>
	void chkmax(T &x, const T y) { x = x < y? y : x; }
	template <class T>
	void chkmin(T &x, const T y) { x = x > y? y : x; }
} using namespace Initial;

struct DSU {
	ll d[maxn];
	ll find(ll x) { return d[x] ^ x? d[x] = find(d[x]) : x; }
	void merge(ll x, ll y) { d[find(x)] = find(y); }
} Left, Right;

ll n, siz[maxn], son[maxn], q, ti, dfn[maxn], out[maxn], idfn[maxn];
vector <ll> to[maxn]; ll dep[maxn], mged[maxn];

void dfs1(ll u, ll fa = 0) {
	siz[u] = 1, idfn[dfn[u] = ++ti] = u, dep[u] = dep[fa] + 1;
	for(ll v: to[u])
		if(v ^ fa) {
			dfs1(v, u), siz[u] += siz[v];
			if(siz[v] > siz[son[u]]) son[u] = v;
		} out[u] = ti;
}

struct seg { ll l, r, w; } a[maxn << 1]; ll m;
struct qur { ll l, r, k, id; } b[maxn];

vector <ll> ffc;

void add(ll x) {
	Left.merge(x, x - 1), Right.merge(x, x + 1);
	ll flg = 0;
	if(Left.find(x) < x - 1 && !mged[x - 1]) flg = 1, mged[x - 1] = 1;
	if(Right.find(x) > x + 1 && !mged[x]) flg = 1, mged[x] = 1;
	if(flg) ffc.pb(x);
}

void dfs2(ll u, ll fa = 0) {
	for(ll v: to[u])
		if(v != fa && v != son[u]) {
			dfs2(v, u);
			for(ll i = dfn[v], x; i <= out[v]; i++)
				x = idfn[i], Left.d[x] = Right.d[x] = x;
		}
	if(son[u]) dfs2(son[u], u); ffc.clear();
	for(ll v: to[u])
		if(v != fa && v != son[u])
			for(ll i = dfn[v]; i <= out[v]; i++)
				add(idfn[i]);
	add(u);
	for(ll x: ffc) a[++m] = (seg) { Left.find(x) + 1,
	 Right.find(x) - 1, dep[u] };
}

struct SGT {
	ll mx[maxn << 2];
	void modify(ll p, ll l, ll r, ll x, ll v) {
		chkmax(mx[p], v); if(l == r) return;
		ll mid = l + r >> 1;
		if(x <= mid) modify(p << 1, l, mid, x, v);
		else modify(p << 1|1, mid + 1, r, x, v);
	}
	ll query(ll p, ll l, ll r, ll ql, ll qr) {
		if(ql <= l && r <= qr) return mx[p];
		if(r < ql || qr < l) return -inf;
		ll mid = l + r >> 1;
		return max(query(p << 1, l, mid, ql, qr),
		 query(p << 1|1, mid + 1, r, ql, qr));
	}
} tr; ll ans[maxn], tree[maxn];

void tr_add(ll x, ll v) {
	for(; x <= n; x += x & -x) chkmax(tree[x], v);
}

ll tr_ask(ll x) {
	ll v = 0;
	for(; x; x -= x & -x) chkmax(v, tree[x]);
	return v;
}

int main() {
//	freopen("query.in", "r", stdin);
//	freopen("query.out", "w", stdout);
	rd(n);
	for(ll i = 0; i <= n + 1; i++) Left.d[i] = Right.d[i] = i;
	for(ll i = 1; i < n; i++) {
		ll u, v; rd(u), rd(v);
		to[u].pb(v), to[v].pb(u);
	} dfs1(1);
	dfs2(1);
	sort(a + 1, a + 1 + m, [](seg a, seg b) {
		return a.r - a.l > b.r - b.l;
	}); rd(q);
	for(ll i = 1; i <= q; i++)
		rd(b[i].l), rd(b[i].r), rd(b[b[i].id = i].k);
	sort(b + 1, b + 1 + q, [](qur a, qur b) {
		return a.k > b.k;
	});
	for(ll i = 1; i <= n; i++) a[++m] = (seg) { i, i, dep[i] };
	for(ll i = 1, j = 1; i <= q; i++) {
		while(j <= m && a[j].r - a[j].l + 1 >= b[i].k)
			tr.modify(1, 1, n, a[j].l, a[j].w), ++j;
		chkmax(ans[b[i].id], tr.query(1, 1, n, b[i].l, b[i].r - b[i].k + 1));
	} tr = {};
	for(ll i = 1, j = 1; i <= q; i++) {
		while(j <= m && a[j].r - a[j].l + 1 >= b[i].k)
			tr.modify(1, 1, n, a[j].r, a[j].w), ++j;
		chkmax(ans[b[i].id], tr.query(1, 1, n, b[i].l + b[i].k - 1, b[i].r));
	}
	sort(a + 1, a + 1 + m, [](seg a, seg b) {
		return a.r > b.r;
	});
	sort(b + 1, b + 1 + q, [](qur a, qur b) {
		return a.r > b.r;
	});
	for(ll i = 1, j = 1; i <= q; i++) {
		while(j <= m && a[j].r >= b[i].r) tr_add(a[j].l, a[j].w), ++j;
		chkmax(ans[b[i].id], tr_ask(b[i].l));
	}
	for(ll i = 1; i <= q; i++) write(ans[i], "\n");
	return 0;
}
posted @ 2024-12-04 19:21  Sktn0089  阅读(393)  评论(0)    收藏  举报