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;
}

浙公网安备 33010602011771号