[ACNOI2022]唯一可做之题
作者:互联网
题目
题目背景
连续两天写
LCT
+
ddp
\texttt{LCT}+\texttt{ddp}
LCT+ddp,今天一看,感觉仍然
d
d
p
\tt ddp
ddp,加之我已将时间尽耗于
T
1
,
T
2
T_1,T_2
T1,T2,终弃之而去。
可惜,它杀了个回马枪,这次的 d d p \tt ddp ddp 形式非常特别。可惜,我无法跳出我的思维模式,也因此无法超越 D D ( X Y X ) \sf DD(XYX) DD(XYX) 。可惜, O U Y E \sf OUYE OUYE 从来不会犯下这种错误,轻取 A C AC AC 。
题目描述
给出
n
n
n 个点的树,点有点权
v
i
v_i
vi,进行
q
q
q 次操作,有三种类型:单点加
v
i
v_i
vi,子树加
v
i
v_i
vi,查询
∑
1
⩽
i
<
j
⩽
n
[
lca
(
i
,
j
)
=
x
]
⋅
v
i
v
j
\sum_{1\leqslant i<j\leqslant n}[\text{lca}(i,j)=x]\cdot v_iv_j
∑1⩽i<j⩽n[lca(i,j)=x]⋅vivj,输出结果对
2
63
2^{63}
263 取模。
数据范围与约定
max
(
n
,
q
)
⩽
2
×
1
0
5
\max(n,q)\leqslant 2\times 10^5
max(n,q)⩽2×105,但我们应该可以做到更好。修改量
Δ
v
∈
[
0
,
1
0
9
]
\Delta v\in[0,10^9]
Δv∈[0,109],初值
v
i
∈
[
0
,
1
0
9
]
v_i\in[0,10^9]
vi∈[0,109],但这不重要。
思路
本以为是一个矩阵较为庞大的
d
d
p
\tt ddp
ddp 。现在看来,做的无用功实在太多。记
S
x
S_x
Sx 为
x
x
x 子树内的
v
i
v_i
vi 之和,则
a
n
s
(
x
)
=
1
2
(
S
x
2
−
v
x
2
−
∑
y
∈
son
x
S
y
2
)
ans(x)=\frac{1}{2}\left(S_x^{\thinspace 2}-v_x^{\thinspace 2}-\sum_{y\in\text{son}_x} S_y^{\thinspace 2}\right)
ans(x)=21(Sx2−vx2−y∈sonx∑Sy2)
值得注意的是 a n s ( x ) ans(x) ans(x) 并不用于转移,只是求出答案,所以 不需要放入矩阵。同理 S x 2 S_x^{\thinspace 2} Sx2 也不用放入矩阵,说白了,只有 S x S_x Sx 在上传。
重链剖分后,对每个点直接维护轻儿子的 S y 2 S_y^{\thinspace 2} Sy2 之和;自己的 S x S_x Sx 也要维护。求答案,就用已知的 ∑ S y 2 \sum S_y^{\thinspace 2} ∑Sy2 与重儿子 S w S_w Sw 与自己的 S x , v x S_x,v_x Sx,vx 直接计算。用 unsigned long long \texttt{unsigned long long} unsigned long long 可以算出取模 2 64 2^{64} 264 的结果,最后除以 2 2 2 就刚好。
子树修改对于 d d p \tt ddp ddp 并不友好。立刻更新所有点的信息比较困难时,尝试懒标记。把 “子树加” 留在所操作的节点上(子树的根)。对于该点之上的祖先,难以 “看见” 该点的标记,最好提前更新;对于该点子树内的点,则可以查询时再考虑——找出当前子树被哪些懒标记给 “覆盖”,即这些标记作用于整棵子树。那么实际需要求出 ∑ y ∈ son x ( S y + size y ⋅ d ) 2 = ∑ y S y 2 + 2 d ∑ y S y ⋅ size y + d 2 ∑ y size y 2 \sum_{y\in\text{son}_x}(S_y+\text{size}_y\cdot d)^2=\sum_{y}S_y^{\thinspace 2}+2d\sum_{y}S_y\cdot\text{size}_y+d^2\sum_y\text{size}_y^{\thinspace 2} ∑y∈sonx(Sy+sizey⋅d)2=∑ySy2+2d∑ySy⋅sizey+d2∑ysizey2 。
那么,能够再算出 ∑ S y ⋅ size y \sum S_y\cdot \text{size}_y ∑Sy⋅sizey 就好,毕竟 ∑ size y 2 \sum\text{size}_y^{\thinspace 2} ∑sizey2 是定值。存下来即可。当然,是不考虑子树外的懒标记的值。
不需要矩阵,也不再需要线段树维护重链了,因为 S x S_x Sx 具有现实意义,可以用 d f n \tt dfn dfn 上的线段树 O ( log n ) \mathcal O(\log n) O(logn) 直接查询。代码实现也比较容易,时间复杂度 O ( n log n + q log n ) \mathcal O(n\log n+q\log n) O(nlogn+qlogn),其中 n log n n\log n nlogn 也可优化为 O ( n ) \mathcal O(n) O(n),但没必要。
代码
#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG long for LONELINESS)
#include <cctype> // ZXY yydSISTER!!!
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(const uint64_t &x){
if(x > 9) writeint(x/10);
putchar(int(x%10)^48);
}
const int MAXN = 200005;
struct Edge{ int to, nxt; };
Edge e[MAXN<<1]; int head[MAXN], cntEdge;
inline void addEdge(int a, int b){
e[cntEdge].to = b, e[cntEdge].nxt = head[a];
head[a] = cntEdge ++;
}
# define _go(i,x) for(int i=head[x]; ~i; i=e[i].nxt)
unsigned int siz[MAXN];
int heavy[MAXN], fa[MAXN];
void scan(int x){
siz[x] = 1; _go(i,x){
fa[e[i].to] = x, scan(e[i].to);
siz[x] += siz[e[i].to];
if(siz[e[i].to] > siz[heavy[x]])
heavy[x] = e[i].to; // node indice
}
}
unsigned int lsiz[MAXN], v[MAXN];
int dfn[MAXN], ed[MAXN], top[MAXN], dfsClock;
uint64_t lig[MAXN][2], me[MAXN];
void dfs(int x, int bel){
dfn[x] = ++ dfsClock, top[x] = bel;
if(heavy[x]) dfs(heavy[x],bel), me[x] = me[heavy[x]];
_go(i,x) if(e[i].to != heavy[x]){
dfs(e[i].to,e[i].to), me[x] += me[e[i].to];
lsiz[x] += siz[e[i].to]*siz[e[i].to];
lig[x][0] += me[e[i].to]*siz[e[i].to];
lig[x][1] += me[e[i].to]*me[e[i].to];
}
me[x] += v[x], ed[x] = dfsClock;
}
/// @brief add value of @p x by @p qv
void update_chain(int x, const uint64_t &qv){
for(x=top[x]; x!=1; x=top[fa[x]]){
lig[fa[x]][1] += (qv+(me[x]<<1))*qv;
lig[fa[x]][0] += qv*siz[x], me[x] += qv;
}
}
/// @brief add on range and query a single point (for calculate tag)
namespace Tagger{
uint64_t c[MAXN];
void modify(int ql, int qr, const unsigned &qv){
for(int i=ql; i<=dfsClock; i+=(i&-i)) c[i] += qv;
for(int i=qr+1; i<=dfsClock; i+=(i&-i)) c[i] -= qv;
}
uint64_t query(int qid){
uint64_t res = 0;
for(int i=qid; i; i&=(i-1)) res += c[i];
return res;
}
}
/// @brief add on range and query a range sum (for calculate S_x)
namespace SgTree{
uint64_t one[MAXN], two[MAXN];
void _modify(int qid, const uint64_t &qv){
const uint64_t tmp = qv*unsigned(qid);
for(int i=qid; i<=dfsClock; i+=(i&-i))
one[i] += qv, two[i] += tmp;
}
uint64_t _query(int qid){
uint64_t res = 0, sum = 0;
for(int i=qid; i; i&=(i-1))
res += two[i], sum += one[i];
return sum*unsigned(qid+1u)-res;
}
void modify(int ql, int qr, const unsigned &qv){
_modify(ql,qv), _modify(qr+1,-uint64_t(qv));
}
inline uint64_t query(int ql, int qr){
return _query(qr)-_query(ql-1);
}
}
uint64_t getAns(int x){
if(!heavy[x]) return 0u; // leaf
const uint64_t d = Tagger::query(dfn[x]);
uint64_t my = SgTree::query(dfn[x],ed[x]); // real
uint64_t him = SgTree::query(dfn[heavy[x]],ed[heavy[x]]);
uint64_t alone = SgTree::query(dfn[x],dfn[x]);
him = him*him+lig[x][1]+((lig[x][0]<<1)+lsiz[x]*d)*d;
return my*my-alone*alone-him;
}
char opt[10];
int main(){
int n = readint(), q = readint();
memset(head+1,-1,n<<2), cntEdge = 0;
for(int i=1; i!=n; ++i) addEdge(readint(),i+1);
rep(i,1,n) v[i] = readint();
scan(1), dfs(1,1); // basic info
rep(i,1,n) SgTree::modify(dfn[i],dfn[i],v[i]);
for(unsigned x,y; q; --q){
scanf("%s",opt), x = readint();
if(*opt == 'Q'){
writeint(getAns(x)>>1);
putchar('\n');
}
else if(*opt == 'S'){
update_chain(x, y = readint());
SgTree::modify(dfn[x],dfn[x],y);
}
else if(*opt == 'M'){
y = readint();
update_chain(x,uint64_t(siz[x])*y);
SgTree::modify(dfn[x],ed[x],y);
Tagger::modify(dfn[x],ed[x],y);
}
}
return 0;
}
标签:uint64,int,sum,ddp,唯一,dfn,MAXN,之题,ACNOI2022 来源: https://blog.csdn.net/qq_42101694/article/details/123139906