[ACNOI2022]唯一可做之题

220 篇文章 2 订阅
98 篇文章 0 订阅

题目

题目背景
连续两天写 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 1i<jn[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(Sx2vx2ysonxSy2)

值得注意的是 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} ysonx(Sy+sizeyd)2=ySy2+2dySysizey+d2ysizey2

那么,能够再算出 ∑ S y ⋅ size y \sum S_y\cdot \text{size}_y Sysizey 就好,毕竟 ∑ 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值