乾岩
我:G 题来咯!!!
大火:这 G 题,大家都不敢做,说是有人在里面放了毒瘤。
我:做,做,为什么不做!不做也别想活着!!!
(两天后)
我:我的 G 题完成辣!!!!!!
AB
不讲
C
显然 $ 2^a * 2 = 2^{a+1} $。
考虑用一个栈存球的大小是 $ 2 $ 的多少次方,每次插入球后,不断取出后面两个球,大小相同则合并,否则插入下一个球。
// Problem: C - Merge the balls
// Contest: AtCoder - AtCoder Beginner Contest 351
// URL: https://atcoder.jp/contests/abc351/tasks/abc351_c
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
// 简短的代码,qwq
int main() {
int n;
scanf("%d", &n);
stack<int> stk;
for (int i = 0; i < n; i++) {
int x;
scanf("%d", &x);
while (stk.size() && stk.top() == x) { // 大小相同
stk.pop();
x++; // 则合并
}
stk.push(x);
}
printf("%d", stk.size());
}
D
首先,我们把 #
的上下左右的 .
都改成 ,
。
众所周知,这道题可以 dfs $ O(NM) $ 遍,但是速度太慢,为 $ O(N^2 M^2) $。
考虑到一个 .
的 4-联通块里每个格子的答案都是一样的,所以就可以优化到 $ O(NM) $。
现在考虑如何统计个数。
如果每次 dfs 前都将 vis
数组清 0,那么时间复杂度又会退化到 $ O(N^2 M^2) $。
所以我们可以在 dfs 时携带一种颜色进行 dfs,遇到逗号都加上。
// Problem: D - Grid and Magnet
// Contest: AtCoder - AtCoder Beginner Contest 351
// URL: https://atcoder.jp/contests/abc351/tasks/abc351_d
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
int n, m;
char s[1005][1005];
int vis[1005][1005];
void dfs(int r, int c, int& ans, int color) { // dfs
if (!(1 <= r && r <= n && 1 <= c && c <= m)) {
return;
} else if (vis[r][c] == color) { // 其它颜色也不返回,因为有逗号
return;
}
vis[r][c] = color;
ans++; // 先加再说
if (s[r][c] == ',') {
return;
}
// 为什么不考虑 '#' 呢,因为我们根本没法进入 '#'(都要先进入 ',')
dfs(r + 1, c, ans, color);
dfs(r - 1, c, ans, color);
dfs(r, c + 1, ans, color);
dfs(r, c - 1, ans, color);
}
int main() {
scanf("%d %d", &n, &m);
memset(s, '.', sizeof s);
for (int i = 1; i <= n; i++) {
scanf("%s", s[i] + 1);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i][j] == '#') { // ASCII 码序:'.' > ',' > '#'
s[i - 1][j] = min(s[i - 1][j], ',');
s[i + 1][j] = min(s[i + 1][j], ',');
s[i][j - 1] = min(s[i][j - 1], ',');
s[i][j + 1] = min(s[i][j + 1], ',');
}
}
}
int ans = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!vis[i][j] && s[i][j] == '.') { // 进行 dfs
int x = 0;
dfs(i, j, x, i * n + j); // 直接用格子在一维上的编号作为颜色
ans = max(ans, x);
}
}
}
printf("%d", ans);
}
E
首先,按照兔子的跳跃方式,我们可以把格子分成两类:$ x + y $ 是奇数和 $ x + y $ 是偶数。同类格子一定可以互相抵达,异类一定不行。
其次,只要你盯了这题足够长时间(?,你就会发现同类格子之间的距离是切比雪夫距离。
再次,你可以发现这个玩意改成曼哈顿距离很好算。
最后,你 bdfs,找到了切比雪夫转曼哈顿的办法。
转换上边说了,这里只说曼哈顿距离和的求法。
曼哈顿距离的两维是独立的,所以可以考虑一条直线上的情况。答案等于图中(请自行想象)所有的线段长度和。
那么,$ i \sim i + 1 $ 这条小线段就对答案贡献了 $ i \times (n - i) $ 次,即左边的点数乘右边的点数,请读者自行证明,易证,不难(((
最后,伸手党们,我把链接丢给你们,行了吧!!!
// Problem: E - Jump Distance Sum
// Contest: AtCoder - AtCoder Beginner Contest 351
// URL: https://atcoder.jp/contests/abc351/tasks/abc351_e
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// oi.wiki/geometry/distance/#曼哈顿距离与切比雪夫距离的相互转化
#include <bits/stdc++.h>
using namespace std;
#define ll long long
pair<int, int> odd[200005], even[200005];
bool cmpbyse(pair<int, int> a, pair<int, int> b) { // 默认以 x 排序,但我们还需要按 y 排一次
return (a.second < b.second);
}
int main() {
int n;
scanf("%d", &n);
int on = 0, en = 0; // 两种点的数量
for (int i = 0; i < n; i++) {
int x, y;
scanf("%d %d", &x, &y);
if ((x + y) & 1) {
odd[on++] = make_pair(x + y, x - y); // 懒得用浮点,直接除以 2(计算时)
} else {
even[en++] = make_pair(x + y, x - y);
}
}
ll ans = 0;
sort(odd, odd + on); // 奇点 x
for (int i = 0; i < on - 1; i++) {
ans += 1ll * (i + 1) * (on - i - 1) * (odd[i + 1].first - odd[i].first) / 2; // 我是 0 下标的,所以还得 +1
}
sort(odd, odd + on, cmpbyse); // 奇点 y
for (int i = 0; i < on - 1; i++) {
ans += 1ll * (i + 1) * (on - i - 1) * (odd[i + 1].second - odd[i].second) / 2;
}
sort(even, even + en); // 偶点 x
for (int i = 0; i < en - 1; i++) {
ans += 1ll * (i + 1) * (en - i - 1) * (even[i + 1].first - even[i].first) / 2;
}
sort(even, even + en, cmpbyse); // 偶点 y
for (int i = 0; i < en - 1; i++) {
ans += 1ll * (i + 1) * (en - i - 1) * (even[i + 1].second - even[i].second) / 2;
}
printf("%lld", ans);
}
F
本次 F 题很水,至少比 E 水,有可能比 D 水,有人说比 C 水(那位哥们甚至打了三个大于号),这就太过分了吧(恼
一个数对答案造成的贡献来源于所有比它小的数,所以可以开一个树状数组。
假如数组是升序的,那么 $ i $ 对答案的贡献为:
\[C_i = \sum_{j = 1}^i A_i - A_j = iA_i - \sum_{j=1}^{i} A_j \]所以可以开两个树状数组,一个记录和,一个记录个数。
数很大,要离散化。
// Problem: F - Double Sum
// Contest: AtCoder - AtCoder Beginner Contest 351
// URL: https://atcoder.jp/contests/abc351/tasks/abc351_f
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll cn[400005], cs[400005]; // cn 统计个数,cs 统计和
void update(ll* c, int pos, int val) { // 树状数组部分
pos++;
while (pos < 400003) {
c[pos] += val;
pos += (pos & -pos);
}
}
ll query(ll* c, int pos) {
pos++;
ll ans = 0;
while (pos) {
ans += c[pos];
pos -= (pos & -pos);
}
return ans;
}
ll num[400005]; // 离散化
ll a[400005];
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
num[i] = a[i];
}
sort(num, num + n);
ll ans = 0;
for (int i = 0; i < n; i++) {
int pos = lower_bound(num, num + n, a[i]) - num;
ans += query(cn, pos - 1) * a[i] - query(cs, pos - 1); // numA_i - sum
update(cn, pos, 1), update(cs, pos, a[i]); // 更新
}
printf("%lld", ans);
}
G
曾经写过的代码最长的题。
对于这种树上修改的题就直接放一个树剖上去准没错。()
考虑对于每个点 $ i $ 定义一个函数 $ g_i(x) $,代表如果它的重儿子的 hash 为 $ x $,那么这个点的 hash 是多少。很显然 $ g_i(x) = Sx + A_i $,其中 $ S $ 为 $ i $ 的轻儿子的 hash 积。
当我们查询一个点 $ i $ 的 hash 值 $ f(i) $ 时,就可以直接用 $ f(i) = g_i(f(hson_i)) $。当然 $ f(hson_i) $ 也可以这么展开,最后整个函数就变成了 $ f(i) = g_{p_1}(g_{p_2}(...g_{p_k}(0))) $,其中 $ p $ 是以 $ i $ 打头的重链。
那么考虑到 $ g $ 是一堆一元一次函数,所以可以用线段树维护。具体做法为 $ a(cx + d) + b = acx + ad + b $。
另外,在更新时我们也需要高效的更新 $ S $。考虑从 $ 1 $ 到 $ u $ 的路径上哪些点的 $ S $ 会发生变化。
观察可得,在这条路径上,$ x $ 的父节点的 $ g $ 被更新,说明 $ x $ 是轻儿子。再由树剖可得,轻儿子不会超过 $ O(log \ dep) $ 个。
所以不断往上跳重链,每次将链顶和链顶的 father 更新一下即可。求 $ S $ 可以用逆元,我学题解用了 bfs 序。
(下面的节点怎么办呢?直接烂尾,反正求最终答案用不着它们。)
// Problem: G - Hash on Tree
// Contest: AtCoder - AtCoder Beginner Contest 351
// URL: https://atcoder.jp/contests/abc351/tasks/abc351_g
// Memory Limit: 1024 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define mod 998244353
#define ll long long
int n;
ll nodeval[200005], initf[200005], initk[200005], initb[200005];
namespace hld {
vector<int> graph[200005];
int fa[200005], sz[200005], hson[200005];
int dfn[200005], rnk[200005], top[200005]; // 树剖
int bfnl[200005], bfnr[200005], rbfn[200005], allh[200005]; // bfs 序,最后一个儿子的 bfs 序,反 bfn(just like rnk),u 打头的重链
// 树剖剖树
void dfs1(int u) {
sz[u] = 1;
hson[u] = -1;
for (int v : graph[u]) {
dfs1(v);
sz[u] += sz[v];
if (hson[u] == -1 || sz[v] > sz[hson[u]]) {
hson[u] = v;
}
}
}
int _tim = 0;
int dfs2(int u, int tp) { // 树剖同时求出初始 hash 与函数
initk[u] = 1, initb[u] = nodeval[u], initf[u] = nodeval[u];
top[u] = tp;
dfn[u] = _tim++;
rnk[dfn[u]] = u;
if (hson[u] == -1) {
initk[u] = 0;
return allh[u] = u;
}
int ans = dfs2(hson[u], tp);
for (int v : graph[u]) {
if (v != hson[u]) {
dfs2(v, v);
initk[u] = (initk[u] * initf[v]) % mod;
}
}
initf[u] = (initk[u] * initf[hson[u]] + initb[u]) % mod;
return allh[u] = ans; // 顺着重儿子找最重的节点
}
void bfs() { // bfs 序
queue<int> que;
que.push(0);
int tim = 0;
while (que.size()) {
int u = que.front();
que.pop();
bfnl[u] = tim++;
rbfn[bfnl[u]] = u;
if (u) {
bfnr[fa[u]] = max(bfnr[fa[u]], bfnl[u]);
}
if (hson[u] == -1) {
continue;
}
que.push(hson[u]);
for (int v : graph[u]) {
if (v != hson[u]) {
que.push(v);
}
}
}
}
};
using namespace hld;
namespace func_seg {
// 一个用来记录一元一次函数的 segtree
struct Node {
ll k, b;
Node(ll _k = -1, ll _b = -1) {
k = _k, b = _b;
}
} tree[800005];
Node merge(Node a, Node b) {
return Node(a.k * b.k % mod, (a.k * b.b + a.b) % mod);
}
void build(int l, int r, int p) {
if (l == r) {
tree[p] = Node(initk[rnk[l]], initb[rnk[l]]);
} else {
int mid = (l + r) / 2;
build(l, mid, p * 2);
build(mid + 1, r, p * 2 + 1);
tree[p] = merge(tree[p * 2], tree[p * 2 + 1]);
}
}
void update(int pos, Node val, int l, int r, int p) {
if (l == r) {
tree[p] = val;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) {
update(pos, val, l, mid, p * 2);
} else {
update(pos, val, mid + 1, r, p * 2 + 1);
}
tree[p] = merge(tree[p * 2], tree[p * 2 + 1]);
}
Node query(int L, int R, int l, int r, int p) {
if (L <= l && r <= R) {
return tree[p];
}
int mid = (l + r) / 2;
Node ans = Node(1, 0);
if (L <= mid) {
ans = merge(ans, query(L, R, l, mid, p * 2));
}
if (mid < R) {
ans = merge(ans, query(L, R, mid + 1, r, p * 2 + 1));
}
return ans;
}
};
using func_seg::Node;
namespace lp_seg {
// 记录 light product 的 segtree,其实就是普通的乘法线段树
ll prod[800005];
void build(int l, int r, int p, ll* a) {
if (l == r) {
prod[p] = a[rbfn[l]];
} else {
int mid = (l + r) / 2;
build(l, mid, p * 2, a);
build(mid + 1, r, p * 2 + 1, a);
prod[p] = prod[p * 2] * prod[p * 2 + 1] % mod;
}
}
void update(int pos, ll val, int l, int r, int p) {
if (l == r) {
prod[p] = val;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) {
update(pos, val, l, mid, p * 2);
} else {
update(pos, val, mid + 1, r, p * 2 + 1);
}
prod[p] = prod[p * 2] * prod[p * 2 + 1] % mod;
}
ll query(int L, int R, int l, int r, int p) {
if (L <= l && r <= R) {
return prod[p];
}
int mid = (l + r) / 2;
ll ans = 1;
if (L <= mid) {
ans = ans * query(L, R, l, mid, p * 2) % mod;
}
if (mid < R) {
ans = ans * query(L, R, mid + 1, r, p * 2 + 1) % mod;
}
return ans;
}
};
void link_update(int u) { // 林克专属的 update(bushi(其实是连接两棵线段树的 update)
ll k = (graph[u].size() ? lp_seg::query(bfnl[hson[u]] + 1, bfnr[u], 0, n - 1, 1) : 0); // 重求倍数
ll b = nodeval[u]; // 不必多说
func_seg::update(dfn[u], Node(k, b), 0, n - 1, 1);
}
ll gethash(int u) { // hash
return func_seg::query(dfn[u], dfn[allh[u]], 0, n - 1, 1).b;
}
int main() {
int q;
scanf("%d %d", &n, &q);
fa[0] = -1;
for (int i = 1; i < n; i++) {
scanf("%d", &fa[i]);
fa[i]--;
graph[fa[i]].push_back(i);
}
for (int i = 0; i < n; i++) {
scanf("%lld", &nodeval[i]);
}
dfs1(0);
dfs2(0, 0);
bfs();
func_seg::build(0, n - 1, 1);
lp_seg::build(0, n - 1, 1, initf);
while (q--) {
int u;
ll val;
scanf("%d %lld", &u, &val);
u--;
nodeval[u] = val;
func_seg::update(dfn[u], Node(func_seg::query(dfn[u], dfn[u], 0, n - 1, 1).k, val), 0, n - 1, 1); // 只需要改常数就行了
while (top[u]) { // 根节点不用更新
// u ~ top[u]
lp_seg::update(bfnl[top[u]], gethash(top[u]), 0, n - 1, 1); // 更新 hash 值
u = fa[top[u]];
link_update(u); // 更新
}
printf("%lld\n", gethash(0));
}
}
标签:Atcoder,ABC,200005,int,题解,ll,pos,++,ans
From: https://www.cnblogs.com/AProblemSolver/p/18166619