线段树维护扫描线
你需要实现一种数据结构支持以下操作:
- 区间加减
保证加减区间一一对应,且先加后减,序列中永远不出现负数。- 查询完整序列中 0 的个数
这个问题,可以用李超线段树合并来解决。
#include<stdio.h>
#include<algorithm>
using namespace std;
#define ll long long
#define i128 __int128
const int MAXN=100005;
int n,a[MAXN],w[MAXN],f[MAXN],cnt,elast[MAXN];
ll A[MAXN],B[MAXN],siz[MAXN];
struct edge{
int v,nxt;
}E[MAXN];
inline void add(int u,int v){
cnt++;
E[cnt].v=v;
E[cnt].nxt=elast[u];
elast[u]=cnt;
}
void dfs1(int u){
siz[u]=a[u];
for(int i=elast[u];i;i=E[i].nxt){
dfs1(E[i].v);
siz[u]=siz[u]+siz[E[i].v];
}
}
i128 Ans[MAXN];
void dfs2(int u){
A[u]=-w[u],B[u]=(siz[1]-2*siz[u])*w[u];
A[u]=A[u]+A[f[u]],B[u]=B[u]+B[f[u]];
for(int i=elast[u];i;i=E[i].nxt){
Ans[0]=(Ans[0]+(i128)w[E[i].v]*siz[E[i].v]*(siz[1]-siz[E[i].v]));
dfs2(E[i].v);
}
}
int Root[MAXN],tot,P[105],Pl[105],Pr[105],CntP;
struct Tree{
int lson,rson,id1,id2;
}T[100*MAXN];
inline ll Y(int id,ll x){
return x*A[id]+B[id];
}
inline void AddMax(int &p,int l,int r,int id){
if(!p){
tot++;
p=tot;
}
if(T[p].id1==0){T[p].id1=id;return;}
int Mid=(l+r)>>1;
if(Y(id,Mid)>Y(T[p].id1,Mid))swap(T[p].id1,id);
if(Y(id,l)>Y(T[p].id1,l))AddMax(T[p].lson,l,Mid,id);
if(Y(id,r)>Y(T[p].id1,r))AddMax(T[p].rson,Mid+1,r,id);
return;
}
inline void AddMin(int &p,int l,int r,int id){
if(!p){
tot++;
p=tot;
}
if(T[p].id2==0){T[p].id2=id;return;}
int Mid=(l+r)>>1;
if(Y(id,Mid)<Y(T[p].id2,Mid))swap(T[p].id2,id);
if(Y(id,l)<Y(T[p].id2,l))AddMin(T[p].lson,l,Mid,id);
if(Y(id,r)<Y(T[p].id2,r))AddMin(T[p].rson,Mid+1,r,id);
return;
}
int Merge(int &u,int v,int l,int r){
if(u==0||v==0){
return u+v;
}
if(l==r){
if(Y(T[v].id1,l)>Y(T[u].id1,l))T[u].id1=T[v].id1;
if(Y(T[v].id2,l)<Y(T[u].id2,l))T[u].id2=T[v].id2;
return u;
}
int Mid=(l+r)>>1;
if(T[u].id1==0)T[u].id1=T[v].id1;
else if(T[v].id1!=0){
if(Y(T[u].id1,Mid)<Y(T[v].id1,Mid))swap(T[u].id1,T[v].id1);
if(Y(T[v].id1,l)>Y(T[u].id1,l))AddMax(T[u].lson,l,Mid,T[v].id1);
if(Y(T[v].id1,r)>Y(T[u].id1,r))AddMax(T[u].rson,Mid+1,r,T[v].id1);
}
if(T[u].id2==0)T[u].id2=T[v].id2;
else if(T[v].id2!=0){
if(Y(T[u].id2,Mid)>Y(T[v].id2,Mid))swap(T[u].id2,T[v].id2);
if(Y(T[v].id2,l)<Y(T[u].id2,l))AddMin(T[u].lson,l,Mid,T[v].id2);
if(Y(T[v].id2,r)<Y(T[u].id2,r))AddMin(T[u].rson,Mid+1,r,T[v].id2);
}
T[u].lson=Merge(T[u].lson,T[v].lson,l,Mid);
T[u].rson=Merge(T[u].rson,T[v].rson,Mid+1,r);
return u;
}
struct node{
ll Max,Min;
};
const int Lim=1000000000;
inline node Query(ll x,int p){
int l=-Lim,r=Lim;
ll Max=LONG_LONG_MIN,Min=LONG_LONG_MAX;
while(p!=0){
if(T[p].id1)Max=max(Max,Y(T[p].id1,x));
if(T[p].id2)Min=min(Min,Y(T[p].id2,x));
int Mid=(l+r)>>1;
if(x<=Mid)p=T[p].lson,r=Mid;
else p=T[p].rson,l=Mid+1;
}
return node{Max,Min};
}
int S[35],cntN;
void GetAns(int u){
if(u!=1){
AddMax(Root[u],-Lim,Lim,u);
AddMin(Root[u],-Lim,Lim,u);
}
for(int i=elast[u];i;i=E[i].nxt){
GetAns(E[i].v);
Root[u]=Merge(Root[u],Root[E[i].v],-Lim,Lim);
}
if(u!=1){
node Temp=Query(siz[1]-siz[u],Root[u]);
Ans[u]=Ans[0]+max((i128)Temp.Max*(siz[1]-siz[u]),(i128)Temp.Min*(siz[1]-siz[u]))-(i128)Y(u,siz[1]-siz[u])*(siz[1]-siz[u]);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=2;i<=n;i++){
scanf("%d %d",&f[i],&w[i]);
add(f[i],i);
}
dfs1(1),dfs2(1);GetAns(1);
for(int i=2;i<=n;i++){
cntN=0;
bool f=false;
if(Ans[i]==(i128)0){
printf("0\n");
continue;
}
if(Ans[i]<0)f=true,Ans[i]=-Ans[i];
while(Ans[i])cntN++,S[cntN]=Ans[i]%10,Ans[i]=Ans[i]/10;
if(f)printf("-");
for(int i=cntN;i>=1;i--)printf("%d",S[i]);
printf("\n");
}
return 0;
}
你需要实现一种数据结构支持以下操作:
- 查询 \(\sum\limits_{l=L}^R\sum\limits_{r=l}^R w(l,r)\)
P3246 [HNOI2016] 序列
题意:查询 \(\sum\limits_{l=L}^R\sum\limits_{r=l}^R w(l,r)\),其中 \(w(l,r)=\min\limits_{i=l}^r a_i\)。
我们假设现在使用扫描线,并预处理 \(lst_i\),代表第一个 \(\leq a_i\) 的数所在的位置。
而线段树上维护的则是 \(Min_{i,r}\),代表当前扫到 \(r\) 时,\(\min\limits_{j=i}^r a_j\) 具体的值是多少。每一次 \(r\) 更新时,我们就称线段树新开了一个版本。
那对于一个 \((L,R)\) 的询问,我们想要的其实是 \(\sum\limits_{i=L}^R \sum\limits_{j=L}^R Min_{i,j}\),这个东西其实就是所谓的区间历史版本区间和。(若 \(i>j\),则令 \(Min_{i,j}\) 值为 \(0\)。)
既然是区间历史版本区间和,那么就可以把第一个区间拆成前缀,即维护前缀历史版本区间和,因为在一个版本上的区间和是容易处理的。
做过 PVZ 的都知道,这个东西可以用矩阵来处理。对于每个线段树节点开矩阵 \(\begin{bmatrix} f &g \\ 0 &0\\ \end{bmatrix}\),其中 \(f\) 表示它所管辖区间的当前版本的 \(\min\) 之和,\(g\) 表示它所管辖区间的前缀版本的 \(\min\) 之和。每次版本更新,就在当前已经有意义的下标上乘以矩阵 \(\begin{bmatrix} 1 &1 \\ 0 &1\\ \end{bmatrix}\),然后 \(f\) 就会被累加到 \(g\) 上,然后呢修改啊查询啊都是简单的了,这里的修改应该是个区间给 \(f\) 赋值,可以做,查询就是查区间 \(g\) 和,也可以做,反正是简单的。
然后呢也可以发现实际上不用维护矩阵,单维护两个数就行了,不再赘述。
LuoTianyi and the Function
LuoTianyi 是谁,真不熟。
经过简单转化,发现这就是区间赋值,区间历史版本区间和线段树,然后就做完了。
值得一提的是,这题告诉我们历史版本的区间与区间和的区间不一定非得有关系。
树
题意:查询 \(\sum\limits_{l=L}^R\sum\limits_{r=l}^R w(l,r)\),其中 \(w(l,r)\) 是给定树上的 \(L,L+1,\dots,R\) 这些点构成的虚树的边集大小。
链的做法就是区间赋值,区间历史版本区间和,因此原问题一定不弱于区间赋值,区间历史版本区间和。
我们考察点集大小之和,最后只要减一减就好了。为了考察点集大小,我们考察每个点什么时候被包含。设 \(t_R(x)\) 代表满足 \(L,L+1,\dots,R\) 所建虚树包含 \(x\) 的最大 \(L\)。假设现在版本由 \(R\) 变成 \(R+1\),那么 \(t_{R+1}(x)\) 会怎么变呢?首先,\(t_{R+1}(x)\) 一定 \(\geq t_R(x)\),这一点是显然的。
事实上,我们有一个结论,即加入 \(R+1\) 时,\((R,R+1)\) 路径上所有的点的 \(t\) 值都应该被赋值为 \(R\),除了 \(R+1\) 本身。为什么?如果当前这个点在路径之外,那么它的出现版本就与 \(R+1\) 是没有关系的。因此,如果在版本 \(R\) 时,删去 \(t_R(x)\) 会导致 \(x\) 不再出现,那么在版本 \(R+1\) 时,删去 \(t_R(x)\) 同样会导致 \(x\) 不再出现。由此可得,此时 \(t_{R+1}(x)=t_R(x)\)。
如果这个点在路径之内且不为 \(R+1\),那么显然保留 \(\{R,R+1\}\) 就足够将其覆盖了。
如果这个点为 \(R+1\),那么显然保留 \(\{R+1,R+1\}\) 就足够将其覆盖了。
但是,我们现在需要的是点集大小。现在在版本 \(R+1\)。如果原来的 \(t_R(x)\) 变成了 \(t_{R+1}(x)\),那么相当于 \((t_R(x),t_{R+1}(x)]\) 这个区间的值都要加 \(1\)。发现这是一个区间加,区间历史版本区间和问题。
修改操作其实是很恶心的。表面看上去是个链覆盖,可以树剖,其实对于每个点的改变都要做一次上述区间加操作!
如果真这样做时间复杂度就上天了,但好在相同的值可以一起做。并且发现,访问这 \(O(\log n)\) 条重链后,路径上的点会被我们改成同一种值。均摊一下发现区间加操作只有 \(O(n)\) 次,但是有树剖的一些操作,所以时间复杂度是 \(O(n\log^2 n)\) 的。然后就做完了。
经过一番思考,我发现我并不会区间加,区间历史版本区间和。

浙公网安备 33010602011771号