NFLSOJ 10267 - 「2020NOIP模拟题-中山纪念2」铲雪(树链剖分+树状数组+势能分析)
作者:互联网
怎么场均一道不可写题啊.jpg
首先看到这样的题面我们可以想到 AGC010C Cleaning,因此我们第一反应肯定是用路径合并的思想去解决这个问题,尝试写一个程序后你会发现每个点向上延伸出去的路径数量就固定的:为其与父亲相连的边的权值。因此其实每个点的贡献是独立的,因此我们可以对每个点分开来考虑,而根据经典模型,每个点延申出去的路径个数的下界为 \(a_i·(sum-2\min(\lfloor\dfrac{sum}{2}\rfloor,sum-mx))\),其中 \(sum\) 为与该点相邻的边权值之和,\(mx\) 为与该点相邻的边权值的最大值。
于是现在问题转化为如何维护这东西,前面 \(a_i·sum\) 的贡献是容易维护的,这里不再赘述,难点在于如何维护 \(a_i·\min(\lfloor\dfrac{sum}{2}\rfloor,sum-mx)\) 的贡献。考虑对树进行轻重链剖分,并将待修改路径剖成 \(\log\) 段重链,对于每段重链开头和结尾的点,我们直接暴力维护删除其原来的贡献并加入新贡献,这部分维护手法较为明显,不再赘述。对于重链中间的点,相当于每次令其与父亲的边以及其与重儿子的边的权值加上 \(v\),我们需要快速维护其贡献的变化。我们将这样的节点分为三类:
- 第一类:不存在绝对众数。
- 第二类:存在绝对众数,且在重链上。
- 第三类:存在绝对众数,且不在重链上。
不难发现,对于第一类节点,进行变换以后仍然是第一类节点,且其贡献恰好会增加 \(w·a_i\);对于第二类节点,进行变换以后仍然是第二类节点,且其贡献也恰好会增加 \(w·a_i\),而对于第三类节点,有的会变成 12 类节点,贡献的变化不好直接描述,否则,如果其继续保持三类节点,那么其贡献会增加 \(2w·a_i\),并且我们还可以发现,变成 12 类节点的三类点个数是均摊 \(\log n\) 的,因为每次只有切换轻重链的边缘节点才会变成三类节点,因此三类点总出现次数为 \(n\log n\)。因此我们考虑这样一个思想:对于重链中的节点,我们暴力找出所有变成 12 类节点的三类点,其余点的贡献可以用树状数组算出。怎么找出所有变成 12 类节点的三类点呢?对于一个三类点,我们记其键值为 \(2mx-sum\),那么一个三类点会变成 12 类点,当且仅当其键值 \(\le 2w\),线段树上二分即可,需要支持区间加,查询某个区间中第一个小于等于某个值的位置。
时间复杂度 \(n\log ^2n\)。值得注意的是,该算法似乎在随机树数据下跑得最慢,因此需要特判深度 \(\le 100\) 才能过。
const int MAXN = 2e5;
int n, qu, a[MAXN + 5];
int hd[MAXN + 5], to[MAXN * 2 + 5], val[MAXN * 2 + 5], nxt[MAXN * 2 + 5], ec = 0;
void adde(int u, int v, int w) {to[++ec] = v; val[ec] = w; nxt[ec] = hd[u]; hd[u] = ec;}
ll suma[MAXN + 5], SUMA = 0, SUMB = 0, sum_adj[MAXN + 5];
namespace HLD {
int fa[MAXN + 5], siz[MAXN + 5], wson[MAXN + 5], dep[MAXN + 5];
int top[MAXN + 5], dfn[MAXN + 5], tim, rid[MAXN + 5];
void dfs1(int x, int f) {
fa[x] = f; siz[x] = 1;
for (int e = hd[x]; e; e = nxt[e]) {
int y = to[e]; if (y == f) continue;
dep[y] = dep[x] + 1; suma[y] = suma[x] + a[y];
dfs1(y, x); siz[x] += siz[y];
if (siz[y] > siz[wson[x]]) wson[x] = y;
}
}
void dfs2(int x, int tp) {
top[x] = tp; rid[dfn[x] = ++tim] = x; if (wson[x]) dfs2(wson[x], tp);
for (int e = hd[x]; e; e = nxt[e]) if (to[e] != wson[x] && to[e] != fa[x]) dfs2(to[e], to[e]);
}
int getlca(int x, int y) {
while (top[x] ^ top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
x = fa[top[x]];
}
return (dep[x] < dep[y]) ? x : y;
}
}
using namespace HLD;
ll get_sum(int x, int y) {
return 2 * (suma[x] + suma[y] - suma[getlca(x, y)] -
suma[fa[getlca(x, y)]]) - a[x] - a[y];
}
namespace data_savers {
multiset<ll> st_light[MAXN + 5];
struct fenwick {
ll t[MAXN + 5];
void add(int x, ll v) {for (int i = x; i <= n; i += (i & (-i))) t[i] += v;}
ll query(int x) {ll ret = 0; for (int i = x; i; i &= (i - 1)) ret += t[i]; return ret;}
} T_sum, T_val, T2, T3;
// T_sum 存储与每个点相邻所有边边权和
// T_val 存储每个点与其父亲相连的边的边权
// T2 存储所有二类点的点权
void add_itvl(fenwick &T, int l, int r, ll v) {T.add(l, v); T.add(r + 1, -v);}
ll query_itvl(fenwick &T, int l, int r) {return T.query(r) - T.query(l - 1);}
struct segtree_solving_3 {
struct node {int l, r; ll mn, lz;} s[MAXN * 4 + 5];
void pushup(int k) {s[k].mn = min(s[k << 1].mn, s[k << 1 | 1].mn);}
void build(int k, int l, int r) {
s[k].l = l; s[k].r = r; if (l == r) return;
int mid = l + r >> 1; build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r);
}
void tag(int k, ll v) {s[k].mn += v; s[k].lz += v;}
void pushdown(int k) {if (s[k].lz) tag(k << 1, s[k].lz), tag(k << 1 | 1, s[k].lz), s[k].lz = 0;}
void modify(int k, int l, int r, ll v) {
if (l > r) return;
if (l <= s[k].l && s[k].r <= r) return tag(k, v), void();
pushdown(k); int mid = s[k].l + s[k].r >> 1;
if (r <= mid) modify(k << 1, l, r, v);
else if (l > mid) modify(k << 1 | 1, l, r, v);
else modify(k << 1, l, mid, v), modify(k << 1 | 1, mid + 1, r, v);
pushup(k);
}
void upd(int k, int p, ll v) {
if (s[k].l == s[k].r) return s[k].mn = v, void();
pushdown(k); int mid = s[k].l + s[k].r >> 1;
if (p <= mid) upd(k << 1, p, v);
else upd(k << 1 | 1, p, v);
pushup(k);
}
int query_fst(int k, int l, int r, ll v) {
if (s[k].mn > v || l > r) return 0;
if (s[k].l == s[k].r) return s[k].l;
pushdown(k); int mid = s[k].l + s[k].r >> 1;
if (l <= s[k].l && s[k].r <= r) {
if (s[k << 1].mn <= v) return query_fst(k << 1, l, mid, v);
else if (s[k << 1 | 1].mn <= v) return query_fst(k << 1 | 1, mid + 1, r, v);
else return 0;
} else if (r <= mid) return query_fst(k << 1, l, r, v);
else if (l > mid) return query_fst(k << 1 | 1, l, r, v);
else {
int p = query_fst(k << 1, l, mid, v);
if (p) return p;
else return query_fst(k << 1 | 1, mid + 1, r, v);
}
}
} segt;
// 对于一个 3 类点,键值为 2mx - sum
// 对于 12 类点,键值为 inf
// 每次暴力改键值 <2w 的点,对于 >2w 的执行区间减。
int gettyp(int x) {
ll mx = (st_light[x].empty()) ? 0 : (*st_light[x].rbegin());
chkmax(mx, T_val.query(dfn[x]));
if (wson[x]) chkmax(mx, T_val.query(dfn[wson[x]]));
if (mx * 2 <= T_sum.query(dfn[x])) return 1;
else if (mx == T_val.query(dfn[x]) || (wson[x] && mx == T_val.query(dfn[wson[x]])))
return 2;
else return 3;
}
ll calc(int x) {
// printf("calc %d\n", x);
ll mx = (st_light[x].empty()) ? 0 : (*st_light[x].rbegin());
chkmax(mx, T_val.query(dfn[x]));
if (wson[x]) chkmax(mx, T_val.query(dfn[wson[x]]));
ll ss = T_sum.query(dfn[x]);
return a[x] * min(ss / 2, ss - mx);
}
void ins(int x, int coef) {
int tp = gettyp(x);
if (tp <= 2) {
T2.add(dfn[x], coef * a[x]);
if (coef != -1) segt.upd(1, dfn[x], 1e18);
} else {
if (coef != -1)
segt.upd(1, dfn[x], 2 * (*st_light[x].rbegin()) - T_sum.query(dfn[x]));
T3.add(dfn[x], 2 * coef * a[x]);
}
}
void init() {
for (int i = 1; i <= n; i++) for (int e = hd[i]; e; e = nxt[e])
if (to[e] != fa[i] && to[e] != wson[i])
st_light[i].insert(val[e]);
for (int i = 1; i <= n; i++) add_itvl(T_sum, dfn[i], dfn[i], sum_adj[i]);
for (int i = 2; i <= n; i++) for (int e = hd[i]; e; e = nxt[e])
if (to[e] == fa[i]) add_itvl(T_val, dfn[i], dfn[i], val[e]);
for (int i = 1; i <= n; i++) SUMB += calc(i);
segt.build(1, 1, n);
for (int i = 1; i <= n; i++) ins(i, 1);
}
}
using namespace data_savers;
int stk3[MAXN + 5], tp = 0;
void deal(int l, int r, int w) {
if (l > r) return;
SUMB += 1ll * w * query_itvl(T2, l, r);
int pre = l - 1;
while (1) {
int pos = segt.query_fst(1, pre + 1, r, 2 * w);
if (!pos) break;
stk3[++tp] = rid[pos]; pre = pos;
}
}
bool FLG = 1;
void modify(int u, int v, int w) {
int x, y;
// 撤销原来的贡献
if (FLG) {
x = u, y = v;
while (dep[x] > dep[y]) SUMB -= calc(x), x = fa[x];
while (dep[y] > dep[x]) SUMB -= calc(y), y = fa[y];
while (x ^ y) SUMB -= calc(x), SUMB -= calc(y), x = fa[x], y = fa[y];
SUMB -= calc(x);
} else {
x = u, y = v; tp = 0;
while (top[x] ^ top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
SUMB -= calc(x); ins(x, -1);
deal(dfn[top[x]], dfn[x] - 1, w);
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
SUMB -= calc(x); ins(x, -1);
if (x != y) {
deal(dfn[y] + 1, dfn[x] - 1, w);
SUMB -= calc(y);
ins(y, -1);
}
for (int i = 1; i <= tp; i++) SUMB -= calc(stk3[i]), ins(stk3[i], -1);
}
// 修改
x = u; y = v;
while (top[x] ^ top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
st_light[fa[top[x]]].erase(st_light[fa[top[x]]].find(T_val.query(dfn[top[x]])));
add_itvl(T_sum, dfn[top[x]], dfn[x], 2 * w);
add_itvl(T_val, dfn[top[x]], dfn[x], w);
st_light[fa[top[x]]].insert(T_val.query(dfn[top[x]]));
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
add_itvl(T_sum, dfn[y], dfn[x], 2 * w);
add_itvl(T_val, dfn[y] + 1, dfn[x], w);
add_itvl(T_sum, dfn[u], dfn[u], -w);
add_itvl(T_sum, dfn[v], dfn[v], -w);
// 加入新贡献
if (FLG) {
x = u, y = v;
while (dep[x] > dep[y]) SUMB += calc(x), x = fa[x];
while (dep[y] > dep[x]) SUMB += calc(y), y = fa[y];
while (x ^ y) SUMB += calc(x), SUMB += calc(y), x = fa[x], y = fa[y];
SUMB += calc(x);
} else {
x = u; y = v;
while (top[x] ^ top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
SUMB += calc(x); ins(x, 1); x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
SUMB += calc(x); ins(x, 1);
if (x != y) {
SUMB += calc(y);
ins(y, 1);
}
for (int i = 1; i <= tp; i++) SUMB += calc(stk3[i]), ins(stk3[i], 1);
// 修改 3 类点 -> 3 类点的贡献
x = u, y = v;
while (top[x] ^ top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
segt.modify(1, dfn[top[x]], dfn[x] - 1, -2 * w);
SUMB += w * query_itvl(T3, dfn[top[x]], dfn[x] - 1);
x = fa[top[x]];
}
if (dep[x] < dep[y]) swap(x, y);
if (x != y) {
SUMB += w * query_itvl(T3, dfn[y] + 1, dfn[x] - 1);
segt.modify(1, dfn[y] + 1, dfn[x] - 1, -2 * w);
}
}
}
int main() {
freopen("snow.in", "r", stdin);
freopen("snow.out", "w", stdout);
scanf("%d%d", &n, &qu);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1, u, v, w; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
adde(u, v, w); adde(v, u, w);
SUMA += 1ll * (a[u] + a[v]) * w;
sum_adj[u] += w; sum_adj[v] += w;
}
suma[1] = a[1]; dfs1(1, 0); dfs2(1, 1); init();
for (int i = 1; i <= n; i++) FLG &= (dep[i] <= 100);
printf("%lld\n", SUMA - 2 * SUMB);
while (qu--) {
int x, y, z; scanf("%d%d%d", &x, &y, &z);
SUMA += 1ll * z * get_sum(x, y);
modify(x, y, z);
printf("%lld\n", SUMA - 2 * SUMB);
}
return 0;
}
标签:剖分,NFLSOJ,fa,int,top,模拟题,dep,MAXN,SUMB 来源: https://www.cnblogs.com/ET2006/p/NFLSOJ-10267.html