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\) 加入前的拓扑序集合,则有
直接实现是 \(\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\) 的大小。
则最终答案为
实际上就是枚举区间最小值,然后如果 \(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;
}