首页 > 技术文章 > 【LuoguP3924】康娜的线段树

Cry-For-theMoon 2021-08-07 10:48 原文

传送门

挺妙的一个题。

本文不会带你卡精度,想卡可以看代码()

如果节点 \(i\)(注意不一定是叶子)深度为 \(depth_i\)(从 \(0\) 开始),维护的权值和为 \(sum_i\),则答案为:

\[\sum \frac{sum_i}{2^{depth_i}} \]

但是这样依旧维护不了答案,因为一次修改(注意本题没有lazy) 会修改很多的节点。

然后考虑节点的权值和都来源于叶子节点(即维护区间长度为 \(1\))的权值。考虑仅用所有的叶子节点 \(i\) 表示出最后的期望:

\[\sum sum_i \,\times\,(\frac{1}{1}+\frac{1}{2}+\frac{1}{2^2}+...+\frac{1}{2^{depth_i}}) \]

也就是把每个节点的权值放在了叶子里面,第二个式子里一连串的分数,就分别代表了这个叶子的某个祖先被路过的概率。

所以对一个位置增加 \(x\) 的贡献是确定的。即对于位置 \(i\),如果增加了 \(x\),则总的答案增加 \(x\,\cdot f(i)\) ,然后 \(f(i)\) 在整个运行过程中始终不变,同时仅与 \(depth_i\) 有关。更具体地,\(f(i)=\sum_{j=0}^{depth_i}\frac{1}{2^j}\)。那么计算出 \(f\) 以后,维护 \(f\) 的前缀和,就可以 \(O(1)\) 回答一次操作过后的答案。

\(f\) 就必须先求出每个叶子节点的深度。考虑建出一颗线段树,在 \(O(n \log n)\) 的时间内可以求出,期望得到 70 pts。

其实开O2卡卡常,如果是考场加上ccf少爷机说不定就过了()

发现瓶颈在于求出每个叶子节点的深度,需要优化这个过程到 \(O(n)\) 级别的复杂度。

考虑建树过程中的划分方案是一样的,所以如果我们已经为一段长度为 \(k\) 的区间递归建完了树,那么对于其它长度为 \(k\) 的区间可以除掉递归,直接求深度。记忆化一下即可。有 \(n\) 个不同长度的区间,加上每个位置的深度只会被计算一次,可以在 \(O(n)\) 的时间内解决这个问题。如果理解困难建议看一下代码,非常简短。

所以这题就在 \(O(n)\) 的时间内解决了。

#include<bits/stdc++.h>
#define rep(i,a,b) for(ll i=(a);i<=(b);i++)
#define per(i,a,b) for(ll i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)
#define next Cry_For_theMoon
#define il inline
#define pb(x) push_back(x)
#define is(x) insert(x)
#define sit set<int>::iterator
#define mapit map<int,int>::iterator
#define pi pair<int,int>
#define ppi pair<int,pi>
#define pp pair<pi,pi>
#define fr first
#define se second
#define vit vector<int>::iterator
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef double db;
using namespace std;
const int MAXN=1e6+10;
ll n,m,qwq,a[MAXN],l[MAXN],r[MAXN],x[MAXN];
ll ans;
ll depth[MAXN],power[30],sum[30],pre[MAXN];
pi f[MAXN]; //记忆化 
il void Read(ll& num){
	char ch;
	int flag=1;
	while((ch=getchar()) && (ch<'0' || ch>'9') && (ch!='-'));
	if(ch!='-')num=ch-'0';
	else flag=-1;
	while((ch=getchar()) && (ch>='0' && ch<='9'))num=num*10+ch-'0';
	num*=flag;
}
void dfs(int l,int r,int d){
	if(f[r-l+1]!=mp(0,0)){
		rep(i,1,r-l+1){
			depth[l+i-1]=d+(depth[f[r-l+1].fr+i-1]-f[r-l+1].se);
		}
		return;
	}
	f[r-l+1]=mp(l,d);
	if(l==r){
		depth[l]=d;
		return;
	}
	int mid=(l+r)>>1;
	dfs(l,mid,d+1);dfs(mid+1,r,d+1);
}
int main(){
	power[0]=1;
	rep(i,1,29)power[i]=power[i-1]*2;
	Read(n);Read(m);Read(qwq);
	qwq*=128;
	rep(i,1,n){
		Read(a[i]);
	}
	rep(i,1,m){
		Read(l[i]);Read(r[i]);Read(x[i]);
	}
	dfs(1,n,0);
	sum[0]=qwq;
	rep(i,1,21){
		sum[i]=sum[i-1]+qwq/power[i];
	}
	rep(i,1,n){
		pre[i]=pre[i-1]+sum[depth[i]];
		ans+=sum[depth[i]]*a[i];
	}
	rep(i,1,m){
		ans+=(pre[r[i]]-pre[l[i]-1])*x[i];
		printf("%lld\n",ans/128);
	}
	return 0;
}

推荐阅读