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

T1:

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

时间复杂度 O(klogk+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 都可以在 O(2nn) 的复杂度内简单地用 DP 求出。

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

ansu,v=S[uS,vS]f(S)g(S{v})

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

但是可以继续优化到 O(2nn),对于当前在求 v 这一行的答案,枚举 S,就相当于,对于所有 uSansu,v 都加上一个定值。因此瓶颈在于给定 a(0)a(2n1),对于所有 u 求出 uSa(S)。可以采用如下的方法:

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

最后 ansu 就是答案。

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:

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

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

mxu 表示子树 u 中的权值最大值,lenu 表示子树 u 的大小。

则最终答案为

maxumin(lenu,au+mxu1)

实际上就是枚举区间最小值,然后如果 au+mxu1lenu,那么就说明整个区间合法,lenu 可以用来更新答案。

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

否则如果 mxu+au<|pu|+1 的话,即 pu 相距较远的话,不合法,但是依旧要用 au+mxu1 来更新答案,为什么呢?

因为考虑包含 p 的任意一个长度为 mxu+au1 的区间(当然这个区间还要包含在子树 u 内),这个区间的最小值一定 au,所以该区间的最大值加最小值 mnu+au

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

时间复杂度 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 @   Kobe303  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示