2021-4-3 Luisvacson&SiberiaWolf复习赛 题解
A.Many NOIs
对于区间\([l,r]\)中:
N的个数为左孩子N的个数\(+\)右孩子N的个数,O和I同理
NO的个数为左孩子NO的个数\(+\)右孩子NO的个数\(+\)左孩子N的个数\(\times\)右孩子O的个数,OI的个数同理
NOI的个数为左孩子NOI的个数\(+\)右孩子NOI的个数\(+\)左孩子N的个数\(\times\)右孩子OI的个数\(+\)左孩子NO的个数\(\times\)右孩子I的个数
所以可以用线段树实现区间修改,区间查询。很容易写出代码:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 500005
#define lc(p) (t[(p << 1)])
#define rc(p) (t[(p << 1) + 1])
#define int long long
int n, m;
struct SegmentTree {
int i, o, no, oi, noi, n;
void init()
{
i = o = no = oi = noi = n = 0;
}
} t[MAXN << 2];
int a[MAXN];
inline SegmentTree merge(SegmentTree x, SegmentTree y)
{
SegmentTree ans;
ans.init();
ans.n = x.n + y.n;
ans.o = x.o + y.o;
ans.i = x.i + y.i;
ans.no = x.no + y.no + x.n * y.o;
ans.oi = x.oi + y.oi + x.o * y.i;
ans.noi = x.noi + y.noi + x.n * y.oi + x.no * y.i;
return ans;
}
void pushup(int p)
{
t[p] = merge(lc(p), rc(p));
}
void build(int p, int l, int r)
{
t[p].init();
if (l == r) {
if (a[l] == 1)
t[p].i = 1;
else if (a[l] == 0)
t[p].o = 1;
else
t[p].n = 1;
return;
}
int mid = l + r >> 1;
build(p << 1, l, mid);
build((p << 1) + 1, mid + 1, r);
pushup(p);
}
void change(int p, int l, int r, int i, int k)
{
if (l == r) {
t[p].init();
if (k == 0)
t[p].o = 1;
else if (k == 1)
t[p].i = 1;
else
t[p].n = 1;
return;
}
int mid = l + r >> 1;
if (i <= mid)
change(p << 1, l, mid, i, k);
else
change((p << 1) + 1, mid + 1, r, i, k);
pushup(p);
}
SegmentTree query(int p, int l, int r, int tl, int tr)
{
if (tl <= l && tr >= r)
return t[p];
int mid = l + r >> 1;
SegmentTree res;
res.init();
if (tl <= mid)
res = merge(res, query(p << 1, l, mid, tl, tr));
if (tr > mid)
res = merge(res, query((p << 1) + 1, mid + 1, r, tl, tr));
return res;
}
signed main()
{
ios_base::sync_with_stdio(0);
cin >> n >> m;
register int i;
string s;
cin >> s;
for (i = 0; i < n; ++i) {
if (s[i] == 'N')
a[i + 1] = 2;
else if (s[i] == 'O')
a[i + 1] = 0;
else
a[i + 1] = 1;
}
build(1, 1, n);
int op, x, y;
char ch;
for (i = 1; i <= m; ++i) {
cin >> op >> x;
if (op == 1) {
cin >> ch;
int c;
if (ch == 'I')
c = 1;
else if (ch == 'O')
c = 0;
else
c = 2;
change(1, 1, n, x, c);
} else {
cin >> y;
cout << query(1, 1, n, x, y).noi << '\n';
}
}
return 0;
}
B.扑克牌
对于第\(i\)张牌:
若它在第一堆中,那经过一次洗牌之后会到达\(2\times i -1\)的位置;
若它在第二堆中,那经过一次洗牌之后会到达\((i-\dfrac{n}{2})\times 2\)的位置。
以上证明略,自行模拟即可找出规律。
所以我们的目标其实就是实现如下函数:

我们可以通过这个函数对于每张牌来模拟,直到这张牌的回到一开始的位置,然后统计出循环长度。最后,对于所有求得的循环长度,取最小公倍数即为答案。
注意,在求解的时候可以对于访问过的牌做标记,使得在同一个循环中的所有牌只会被统计一次。同时,对于每一张牌,它要么之前在别的循环中被访问过,要么代表一个新的循环,所以复杂度应该是严格\(O(n)\)的。
#include <bits/stdc++.h>
using namespace std;
int n, k;
bool vis[10000005];
int gcd(int a, int b) {//求解gcd当然不一定要这么求
int acc = 0;
while (!(a & 1) && !(b & 1)) {
++acc;
a >>= 1;
b >>= 1;
}
while (!(a & 1)) a >>= 1;
while (!(b & 1)) b >>= 1;
if (a < b) {
int t = a;
a = b;
b = t;
}
while ((a = (a - b) >> 1)) {
while (!(a & 1)) a >>= 1;
if (a < b) {
int t = a;
a = b;
b = t;
}
}
return b << acc;
}
inline int lcm(int x, int y) { return x / gcd(x, y) * y; }
inline int solve(int x) {
int t = x, cnt = 0;
do {
if (t <= k)
t = (t << 1) - 1;
else
t = (t - k) << 1;
if (t != x && vis[t]) break;
vis[t] = true;
++cnt;
} while (t != x);
return cnt;
}
signed main() {
scanf("%d", &n);
k = n >> 1;
register int i;
int ans = 1;
for (i = 1; i <= n; ++i) {
if (!vis[i]) {
ans = lcm(ans, solve(i));
}
}
printf("%d\n", ans);
return 0;
}
C.西伯利亚金字塔

由于该点与它的最高点距离最长,则只有可能是四个角符合这一要求。分别计算出四个角与该金字塔最高点的距离,并取最大值。这是最大水平距离。题目已经给了高度,再根据勾股定理,即可得出最大距离。
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main() {
int x1, x2, y1, y2, hx, hy, h;
double d1, d2, d3, d4, d;
scanf("%lld%lld%lld%lld%lld%lld%lld", &x1, &x2, &y1, &y2, &hx, &hy, &h);
d1 = sqrt(abs(hy - y1) * abs(hy - y1) + abs(hx - x1) * abs(hx - x1));
d2 = sqrt(abs(hy - y2) * abs(hy - y2) + abs(hx - x2) * abs(hx - x2));
d3 = sqrt(abs(hy - y2) * abs(hy - y2) + abs(hx - x1) * abs(hx - x1));
d4 = sqrt(abs(hy - y1) * abs(hy - y1) + abs(hx - x2) * abs(hx - x2));
d = max(max(d1, d2), max(d3, d4));
printf("%.2f", sqrt(d * d + h * h));
return 0;
}
D.矩形
对于change操作,暴力修改即可
对于query操作,使用悬线法求最大子矩形:
我们定义除两个端点外不覆盖任何障碍点的竖直线段为有效竖线,上端点覆盖了一个障碍点或达到整个矩形上端的有效竖线为悬线。
对于每个点\((i,j)\),我们维护3个值:
\(up_{i,j}\)表示该点能向上扩展的最大长度;
\(l_{i,j}\)表示该点最多能向左扩展到第几列;
\(r_{i,j}\)表示该点最多能向右扩展到第几列。
初始\(up_{i,j}=1,l_{i,j}=r_{i,j}=j\)
显然这三个值可以通过递推预处理:
当\((i,j),(i-1,j),(i,j-1),(i,j+1)\)都不为障碍点时有:
\(up_{i,j}=up_{i-1,j}+1\)
\(l_{i,j}=l_{i,j-1}\)
\(r_{i,j}=r_{i,j+1}\)
根据一条悬线往左边能扩展到的最大长度一定是它包含的所有点能往左边扩展到的最大长度的最小值,我们可以得到状态转移方程 \(l_{i,j}=max(l_{i,j},l_{i-1,j})\),往右同理,即\(r_{i,j}=min(r_{i,j},r_{i-1,j})\)
那么就可以用\(up_{i,j}\times (r_{i,j}-l_{i,j}+1)\)算出所有子矩形面积,统计一下最大值就可以了。
复杂度\(O(nmq)\)
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1005
int n, m;
int a[MAXN][MAXN];
int l[MAXN][MAXN], r[MAXN][MAXN], up[MAXN][MAXN];
inline int solve(int sx, int sy, int gx, int gy, int x) {
int ans = -1 << 30;
memset(l, 0, sizeof(l));
memset(r, 0, sizeof(r));
memset(up, 0, sizeof(up));
register int i, j;
for (register int i = sx; i <= gx; ++i) {
for (register int j = sy; j <= gy; ++j) {
if (a[i][j] != x) {
l[i][j] = r[i][j] = j;
up[i][j] = 1;
}
}
}
for (i = sx; i <= gx; ++i)
for (j = sy + 1; j <= gy; ++j)
if (a[i][j] != x && a[i][j - 1] != x) l[i][j] = l[i][j - 1];
for (i = sx; i <= gx; ++i)
for (j = gy - 1; j >= sy; --j)
if (a[i][j] != x && a[i][j + 1] != x) r[i][j] = r[i][j + 1];
for (i = sx; i <= gx; ++i)
for (j = sy; j <= gy; ++j)
if (a[i][j] != x && a[i - 1][j] != x) up[i][j] = up[i - 1][j] + 1;
for (i = sx; i <= gx; ++i)
for (j = sy; j <= gy; ++j) {
if (i >= sx + 1 && a[i][j] != x && a[i - 1][j] != x) {
l[i][j] = max(l[i][j], l[i - 1][j]);
r[i][j] = min(r[i][j], r[i - 1][j]);
}
ans = max(ans, up[i][j] * (r[i][j] - l[i][j] + 1));
}
return ans;
}
int q;
signed main() {
ios_base::sync_with_stdio(0);
cin >> n >> m;
register int i, j;
for (i = 1; i <= n; ++i) {
for (j = 1; j <= m; ++j) {
cin >> a[i][j];
}
}
cin >> q;
string s;
int sx, sy, gx, gy, x;
while (q--) {
cin >> s;
cin >> sx >> sy >> gx >> gy;
if (s == "change") {
cin >> x;
for (i = sx; i <= gx; ++i) {
for (j = sy; j <= gy; ++j) {
a[i][j] = x;
}
}
} else {
cout << max(solve(sx, sy, gx, gy, 1), solve(sx, sy, gx, gy, 0))
<< endl;
}
}
return 0;
}
E.总统频道

根据“\(n-1\)”这个条件和题目最后的黑体字部分,我们可以很明确地知道这是一棵树,且根节点为1,其切换频道的次数就是每个节点的深度。计算根节点与每个深度为\(t\)的节点之间的路径上最小“总统频道”的个数即可得出答案。
你也可以用简单的树形dp以\(O(n)\)的复杂度计算出根节点与每个节点之间的路径上“总统频道”的个数,再对深度为\(t\)的节点取\(min\)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int head[100005], a[100005], d[100005], b[100005], father[100005], s[100005];
int n, m, t, u, v, sum, cnt, f, k;
bool chan[100005];
struct node {
int to, next;
} edge[200005];
void addedge(int u, int v) {
cnt++;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
void dfs(int u, int fa, int depth) {
father[u] = fa;
d[u] = depth;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa) continue;
dfs(v, u, depth + 1);
}
}
void dfs2(int u, int fa) {
s[u] = s[fa];
if (u != 1) {
if (chan[u]) s[u]++;
}
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa) continue;
dfs2(v, u);
}
}
signed main() {
cin >> n >> m >> t;
for (int i = 1; i <= m; i++) {
cin >> a[i];
chan[a[i]] = true;
}
for (int i = 1; i < n; i++) {
cin >> u >> v;
addedge(u, v);
addedge(v, u);
}
f = 1;
k = 0;
dfs(1, 0, 0);
int maxn = 0, minn = 2147483647;
for (int i = 1; i <= n; i++) {
maxn = max(maxn, d[i]);
if (d[i] == t) {
b[k++] = i;
}
}
if (maxn < t) {
cout << "-1" << endl;
return 0;
}
s[1] = 0;
dfs2(1, 0);
for (int i = 0; i < k; i++) {
minn = min(s[b[i]], minn);
}
cout << minn;
return 0;
}
F.猴面包树
树剖裸题,没什么说的
#include <bits/stdc++.h>
#define int long long
#define lc p * 2
#define rc p * 2 + 1
#define maxn 200005
#define mod 100000007
using namespace std;
int head[maxn];
int id[maxn], rk[maxn], top[maxn];
int f[maxn], size[maxn], d[maxn], son[maxn];
int a[maxn], type[maxn], que[maxn];
int cnt;
struct node {
int next, to;
} edge[maxn * 2];
struct segt {
int l, r, pre, lt;
} t[maxn * 4];
void addedge(int u, int v) {
cnt++;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
void dfs1(int u, int fa, int depth) {
d[u] = depth;
f[u] = fa;
size[u] = 1;
int maxson = -1;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == f[u]) continue;
dfs1(v, u, depth + 1);
size[u] += size[v];
if (maxson < size[v]) {
maxson = size[v];
son[u] = v;
}
}
}
void dfs2(int u, int tp) {
top[u] = tp;
id[u] = ++cnt;
rk[cnt] = u;
if (!son[u]) return;
dfs2(son[u], tp);
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v != f[u] && v != son[u]) {
dfs2(v, v);
}
}
}
void build(int p, int l, int r) {
t[p].l = l;
t[p].r = r;
if (l == r) {
t[p].pre = a[rk[l]];
return;
}
int mid = (l + r) / 2;
build(lc, l, mid);
build(rc, mid + 1, r);
t[p].pre = (t[lc].pre + t[rc].pre) % mod;
}
void pushdown(int p) {
t[lc].pre = (t[lc].pre + t[p].lt * (t[lc].r - t[lc].l + 1)) % mod;
t[rc].pre = (t[rc].pre + t[p].lt * (t[rc].r - t[rc].l + 1)) % mod;
t[lc].lt = (t[lc].lt + t[p].lt) % mod;
t[rc].lt = (t[rc].lt + t[p].lt) % mod;
t[p].lt = 0;
}
void update(int p, int l, int r, int k) {
if (l <= t[p].l && r >= t[p].r) {
t[p].pre = (t[p].pre + k * (t[p].r - t[p].l + 1)) % mod;
t[p].lt = (t[p].lt + k) % mod;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) / 2;
if (l <= mid) update(lc, l, r, k);
if (r > mid) update(rc, l, r, k);
t[p].pre = (t[lc].pre + t[rc].pre) % mod;
}
int query(int p, int l, int r) {
if (l <= t[p].l && r >= t[p].r) {
return t[p].pre;
}
pushdown(p);
int mid = (t[p].l + t[p].r) / 2, ans = 0;
if (l <= mid) ans = (ans + query(lc, l, r)) % mod;
if (r > mid) ans = (ans + query(rc, l, r)) % mod;
return ans % mod;
}
void update2(int u, int v, int k) {
while (top[u] != top[v]) {
if (d[top[u]] < d[top[v]]) swap(u, v);
update(1, id[top[u]], id[u], k);
u = f[top[u]];
}
if (id[u] > id[v]) swap(u, v);
update(1, id[u], id[v], k);
}
int sum(int u, int v) {
int ans = 0;
while (top[u] != top[v]) {
if (d[top[u]] < d[top[v]]) swap(u, v);
ans = (ans + query(1, id[top[u]], id[u])) % mod;
u = f[top[u]];
}
if (id[u] > id[v]) swap(u, v);
ans = (ans + query(1, id[u], id[v])) % mod;
return ans % mod;
}
signed main() {
int n, m, x, u, v;
scanf("%lld%lld%lld", &n, &m, &x);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= m; i++) {
scanf("%lld", &type[i]);
}
for (int i = 1; i <= m; i++) {
scanf("%lld", &que[i]);
}
for (int i = 1; i < n; i++) {
scanf("%lld%lld", &u, &v);
addedge(u, v);
addedge(v, u);
}
cnt = 0;
dfs1(1, 0, 1);
dfs2(1, 1);
cnt = 0;
build(1, 1, n);
for (int i = 1; i <= m; i++) {
if (type[que[i]] == 1) {
scanf("%lld", &u);
update2(1, u, -x);
} else if (type[que[i]] == 2) {
scanf("%lld", &u);
update(1, id[u], id[u] + size[u] - 1, -x);
} else {
scanf("%lld%lld", &u, &v);
printf("%lld\n", sum(u, v));
update2(u, v, -x);
}
}
return 0;
}

浙公网安备 33010602011771号