Public NOIP Round #4(Div. 1) 题解

T1:

容易发现每种药品之间互不影响,对每种药品分别计算,对于它所涉及到的区间开个 vector 存下来,离散化之后差分,然后前缀和,数出只有它一个线段覆盖的段即可。

时间复杂度 \(\mathcal O(\sum k\log\sum k+n+m)\)

Code:

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define lb lower_bound
typedef long long ll;
const int N = 1000005;
int n, m;
int a[N];
int lsh[N], tot;
ll Ans[N], ans;
int sum1[N], sum2[N];
struct node {
	int l, r, id;
	
	node (){}
	node (int _l, int _r, int _id) {
		l = _l, r = _r, id = _id;
	}
};
vector <node> vec[N];

int find(int x) {
	return lb(lsh + 1, lsh + tot + 1, x) - lsh;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) scanf("%d", &a[i]);
	for (int i = 1, l, r, k, x; i <= n; ++i) {
		scanf("%d%d%d", &l, &r, &k);
		while (k--) scanf("%d", &x), vec[x].pb(node(l, r, i));
	}
	for (int i = 1; i <= m; ++i) {
		tot = 0;
		for (auto j : vec[i]) lsh[++tot] = j.l, lsh[++tot] = j.r + 1;
		sort(lsh + 1, lsh + tot + 1), tot = unique(lsh + 1, lsh + tot + 1) - (lsh + 1);
		for (auto &j : vec[i]) j.l = find(j.l), j.r = find(j.r + 1), ++sum1[j.l], --sum1[j.r];
		for (int j = 1; j < tot; ++j) {
			sum1[j] += sum1[j - 1], sum2[j] = sum2[j - 1];
			if (sum1[j]) ans += 1ll * (lsh[j + 1] - lsh[j]) * a[i];
			if (sum1[j] == 1) sum2[j] += lsh[j + 1] - lsh[j]; 
		}
		for (auto j : vec[i]) Ans[j.id] += 1ll * (sum2[j.r - 1] - sum2[j.l - 1]) * a[i];
		for (int j = 1; j <= tot; ++j) sum1[j] = 0;
	}
	for (int i = 1; i <= n; ++i) printf("%lld ", ans - Ans[i]);
	return 0;
}

T2:

\(f(S)\) 表示按照拓扑序从前往后的顺序加点,初始为空集,到已经加入 \(S\) 这个点集的加点方案数,\(g(S)\) 表示按照拓扑序从后往前的顺序删点,初始为全集,到删的只剩 \(S\) 这个点集的删点方案数。

\(f,g\) 都可以在 \(\mathcal O(2^nn)\) 的复杂度内简单地用 DP 求出。

对于给定的 \(u,v\),若 \(u\) 要在 \(v\) 前,就是要,拓扑序加入 \(v\) 的时候,\(u\) 已经在拓扑序里了。于是枚举 \(v\) 加入前的拓扑序集合,则有

\[ans_{u,v}=\sum_{S}[u\in S,v\notin S]f(S)g(S\cup \left\{v\right\}) \]

直接实现是 \(\mathcal O(2^nn^2)\) 的,因为常数小所以能过。

但是可以继续优化到 \(\mathcal O(2^nn)\),对于当前在求 \(v\) 这一行的答案,枚举 \(S\),就相当于,对于所有 \(u\in S\)\(ans_{u,v}\) 都加上一个定值。因此瓶颈在于给定 \(a(0)\sim a(2^n-1)\),对于所有 \(u\) 求出 \(\sum_{u\in S}a(S)\)。可以采用如下的方法:

for (int S = m - 1; S; --S) {
	int u = __builtin_ctz(S);
	ans[u] += a[S], a[S ^ (1 << u)] += a[S];
}

最后 \(ans_u\) 就是答案。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 20;
int T;
int n, m;
int a[N], b[N];
ll ans[N];
ll f[1 << N], g[1 << N], c[1 << N];

void solve() {
	scanf("%d%d", &n, &m);
	memset(a, 0, sizeof (int) * n), memset(b, 0, sizeof (int) * n);
	while (m--) {
		int x, y; scanf("%d%d", &x, &y), --x, --y;
		a[x] |= 1 << y, b[y] |= 1 << x;
	}
	m = 1 << n;
	memset(f, 0, sizeof (ll) * m), memset(g, 0, sizeof (ll) * m);
	f[0] = g[0] = 1;
	for (int i = 0; i < m; ++i)
		for (int j = 0; j < n; ++j) if ((i >> j & 1) == 0) {
			if ((i | b[j]) == i) f[i | (1 << j)] += f[i];
			if ((i | a[j]) == i) g[i | (1 << j)] += g[i];
		}
	for (int v = 0; v < n; ++v) {
		memset(c, 0, sizeof (ll) * m);
		memset(ans, 0, sizeof (ll) * n);
		for (int S = 0; S < m; ++S)
			if ((S | b[v]) == S && (S >> v & 1) == 0) {
				int _S = (m - 1) ^ S ^ (1 << v);
				c[S] = f[S] * g[_S];
			}
		for (int S = m - 1; S; --S) {
			int u = __builtin_ctz(S);
			ans[u] += c[S], c[S ^ (1 << u)] += c[S];
		}
		for (int u = 0; u < n; ++u) printf("%lld ", ans[u]);
		printf("\n");
	}
}

int main() {
	scanf("%d", &T);
	while (T--) solve();
	return 0;
}

T3:

首先有个 \(\mathcal O(nm\log n)\) 的做法,就是直接二分然后拿 ST 表维护区间最大值和最小值,能拿 \(60\)

但是放弃这个想法,考虑建出一棵小根笛卡尔树,那么以某个节点为根的子树就是它作为最小值的区间。

\(mx_u\) 表示子树 \(u\) 中的权值最大值,\(len_u\) 表示子树 \(u\) 的大小。

则最终答案为

\[\max_{u}\min(len_u,a_u+mx_u-1) \]

实际上就是枚举区间最小值,然后如果 \(a_u+mx_u-1\ge len_u\),那么就说明整个区间合法,\(len_u\) 可以用来更新答案。

否则如果最大值的位置,设为 \(p\),和 \(u\) 的位置相距较近的话,形式化的讲,就是 \(mx_u+a_u\ge |p-u|+1\),那么在子树 \(u\) 中一定存在一个长度为 \(a_u+mx_u-1\) 的区间同时包含 \(p,u\),于是 \(a_u+mx_u-1\) 可以用来更新答案。

否则如果 \(mx_u+a_u\lt |p-u|+1\) 的话,即 \(p\)\(u\) 相距较远的话,不合法,但是依旧要用 \(a_u+mx_u-1\) 来更新答案,为什么呢?

因为考虑包含 \(p\) 的任意一个长度为 \(mx_u+a_u-1\) 的区间(当然这个区间还要包含在子树 \(u\) 内),这个区间的最小值一定 \(\ge a_u\),所以该区间的最大值加最小值 \(\ge mn_u+a_u\)

于是发现这样答案只会算小,但是能取到最终答案,如果真实答案是 \([l,r]\),在我们枚举到 \([l,r]\) 最小值的时候就一定会把它算到,我们把所有 \(u\) 都试了一遍,其中肯定能试到 \([l,r]\) 的最小值位置。

时间复杂度 \(\mathcal O(nm)\)

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int n, m;
int a[N];
int son[N][2], stk[N], top;
int mx[N], len[N];
int ans;

void dfs(int x) {
	mx[x] = a[x], len[x] = 1;
	for (int i = 0, y; i < 2; ++i) {
		y = son[x][i];
		if (!y) continue;
		dfs(y);
		mx[x] = max(mx[x], mx[y]), len[x] += len[y];
	}
	ans = max(ans, min(len[x], a[x] + mx[x] - 1));
}

void build() {
	for (int i = 0; i <= n; ++i) son[i][0] = son[i][1] = 0;
	top = ans = 0;
	for (int i = 1; i <= n; ++i) {
		while (top && a[stk[top]] > a[i]) --top;
		son[i][0] = son[stk[top]][1], son[stk[top]][1] = i;
		stk[++top] = i;
	}
	dfs(stk[1]);
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	build();
	printf("%d\n", ans);
	while (m--) {
		int _, x, y; scanf("%d", &_);
		while (_--) scanf("%d%d", &x, &y), swap(a[x], a[y]);
		build();
		printf("%d\n", ans);
	}
	return 0;
}
posted @ 2022-11-24 21:28  Kobe303  阅读(43)  评论(0编辑  收藏  举报