Public NOIP Round #4(Div. 1) 题解
T1:
容易发现每种药品之间互不影响,对每种药品分别计算,对于它所涉及到的区间开个 vector
存下来,离散化之后差分,然后前缀和,数出只有它一个线段覆盖的段即可。
时间复杂度 O(∑klog∑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 都可以在 O(2nn) 的复杂度内简单地用 DP 求出。
对于给定的 u,v,若 u 要在 v 前,就是要,拓扑序加入 v 的时候,u 已经在拓扑序里了。于是枚举 v 加入前的拓扑序集合,则有
直接实现是 O(2nn2) 的,因为常数小所以能过。
但是可以继续优化到 O(2nn),对于当前在求 v 这一行的答案,枚举 S,就相当于,对于所有 u∈S,ansu,v 都加上一个定值。因此瓶颈在于给定 a(0)∼a(2n−1),对于所有 u 求出 ∑u∈Sa(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 的大小。
则最终答案为
实际上就是枚举区间最小值,然后如果 au+mxu−1≥lenu,那么就说明整个区间合法,lenu 可以用来更新答案。
否则如果最大值的位置,设为 p,和 u 的位置相距较近的话,形式化的讲,就是 mxu+au≥|p−u|+1,那么在子树 u 中一定存在一个长度为 au+mxu−1 的区间同时包含 p,u,于是 au+mxu−1 可以用来更新答案。
否则如果 mxu+au<|p−u|+1 的话,即 p 和 u 相距较远的话,不合法,但是依旧要用 au+mxu−1 来更新答案,为什么呢?
因为考虑包含 p 的任意一个长度为 mxu+au−1 的区间(当然这个区间还要包含在子树 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话