[3.26福建四校联考]

来自FallDream的博客,未经允许,请勿转载,谢谢。

------------------------------------------------------------------------------------------

我好菜啊都不会,ditoly轻松切掉T1,险些rank1,我死搞T3,到最后log^2都不会,真的菜。

------------------------------------------------------------------------------------------

A.约会

给定一棵n个节点的树,每个点有权值。m次操作1.单点加2.子树加3.求满足$lca(x,y)=z$的$w[x]+w[y]$的期望值。n,m<=300000

题解:对于以点a为根的一棵子树,假设它有孩子x1..xk,size(x)表示子树x的大小,sum(x)表示子树x的权值和,w(x)表示点x的权值,那么点$lca(x,y)=a$的权值总和为:

$(size(a)-size(x1))*sum(x1)+(size(a)-size(x1)*sum(x2)+..+(size(a)-size(k))*sum(k)+w(a)*(size(a)-1)$

方案数同理,可以看作每一个点对的权值和都是1,那么我们把它展开得到所求的期望值是:

$$frac{2*size(a)*sum(a)-sum_{i=1}^{k}sum(xk)*size(xk)-w(a)}{size(a)^2-sum_{i=1}^{k}size(xk)^{2}-1}$$

两部分可以预处理,那么我们考虑处理修改操作对上面部分的影响。

如果改的是一棵子树,那么可以看作所有点对的期望都实际增加了$2*ad$,我们在一棵线段树上区间改。

如果改的是一个节点,那么代入公式我们会发现它实际增加了$2*ad*(size[x]-1)$,直接记下即可。

处理完了修改操作对子树的影响,接下来要考虑对这个操作到跟节点的一条链上的节点的影响。

同样带入公式,得到对于一个点a,修改的点在它的子树b中,对它的影响是$ad*(size(a)-size(b))$ 

于是我们对这棵树树剖,然后最多只要处理logn条重链,对于重链上的点线段树区间修改,对于重链之间的连接点,直接暴力改它就好啦。

复杂度$nlog^{2}n$

#include<iostream>
#include<cstdio>
#define MN 300000
#define ll long long
#define ld long double
using namespace std;
inline int read()
{
    int x = 0 , f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
ld w1[MN+5];
char op[5];
ll sum[MN+5],w[MN+5],s1[MN+5];
int size[MN+5],mx[MN+5],head[MN+5],cnt=0,dfn=0,n,m,top[MN+5],nl[MN+5],nr[MN+5],fa[MN+6];
struct edge{int to,next;}e[MN+5]; 
struct TREE{int l,r;ll x,val;}T1[MN*4+5],T2[MN*4+5],T3[MN*4+5];
inline void ins(int f,int t){e[++cnt]=(edge){t,head[f]};head[f]=cnt;}

void build(TREE*T,int x,int l,int r)
{
    if((T[x].l=l)==(T[x].r=r))return;
    int mid=l+r>>1;
    build(T,x<<1,l,mid);build(T,x<<1|1,mid+1,r);
}

void pushdown(TREE*T,int x)
{
    int l=x<<1,r=x<<1|1;
    T[l].x+=T[x].val;T[r].x+=T[x].val;
    T[l].val+=T[x].val;T[r].val+=T[x].val;
    T[x].val=0;
}

void renew(TREE*T,int x,int l,int r,ll ad)
{
    if(T[x].l==l&&r==T[x].r) {T[x].x+=ad;T[x].val+=ad;return;}
    int mid=T[x].l+T[x].r>>1;
    if(r<=mid)renew(T,x<<1,l,r,ad);
    else if(l>mid)renew(T,x<<1|1,l,r,ad);
    else {renew(T,x<<1,l,mid,ad);renew(T,x<<1|1,mid+1,r,ad);}
}

ll query(TREE*T,int x,int k)
{
    if(T[x].l==T[x].r)return T[x].x;    
    if(T[x].val!=0)pushdown(T,x);
    int mid=T[x].l+T[x].r>>1;
    return k<=mid?query(T,x<<1,k):query(T,x<<1|1,k);
}

void dfs1(int x)
{
    size[x]=1;mx[x]=0;sum[x]=w[x];s1[x]=-1;w1[x]=-w[x];int maxn=0;
    for(int i=head[x];i;i=e[i].next)
    {
        dfs1(e[i].to);size[x]+=size[e[i].to];sum[x]+=sum[e[i].to];
        if(size[e[i].to]>maxn){maxn=size[e[i].to];mx[x]=e[i].to;}
        w1[x]-=size[e[i].to]*sum[e[i].to];s1[x]-=1LL*size[e[i].to]*size[e[i].to];
    }
    w1[x]+=sum[x]*size[x];s1[x]+=1LL*size[x]*size[x];
} 

void dfs2(int x,int tp)
{
    nl[x]=++dfn;top[x]=tp;if(mx[x]) dfs2(mx[x],tp);
    for(int i=head[x];i;i=e[i].next)
        if(e[i].to!=mx[x])dfs2(e[i].to,e[i].to);
    nr[x]=dfn;
}

void up(int x,ll ad)
{
    for(;x!=0;x=fa[top[x]])
    {
        if(x!=top[x]) renew(T2,1,nl[top[x]],nl[x]-1,ad);
        if(fa[top[x]]) w1[fa[top[x]]]+=ad*(size[fa[top[x]]]-size[top[x]]);
    }
}

int main()
{
    n=read();m=read();build(T1,1,1,n);build(T2,1,1,n);
    for(int i=2;i<=n;i++) ins(fa[i]=read(),i);
    for(int i=1;i<=n;i++) w[i]=read();
    dfs1(1);dfs2(1,1);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",op);int x=read();
        if(op[0]=='Q'){printf("%.6f
",size[x]>1?2*query(T1,1,nl[x])+(double)2*(double)((double)w1[x]+(ld)query(T2,1,nl[x])*(size[x]-size[mx[x]]))/s1[x]:0);}
        else if(op[0]=='S'){int ad=read();w1[x]+=1LL*(size[x]-1)*ad;up(x,ad);}
        else {int ad=read();renew(T1,1,nl[x],nr[x],ad);up(x,1LL*(nr[x]-nl[x]+1)*ad);}
    }
    return 0;
}

 3.山脉

给定n座山,每座山都有一个高度,需要支持两个操作:1)给区间[l,r]加上一个首项为a,公差为d(>0)的等差数列 2)询问有多少个点比它两旁的点都大。$n,mleqslant 10^{5}$

题解:很容易想到改为维护差分数组,这样题目转换为维护一个n-1个点的序列,每次修改两个单点和一段区间,求第一个是正的,第二个是负的的长度为2的区间的个数。

由于区间加的公差d是正的,一开始最多有n-1个负数,每次操作最多加入一个负数,所以我们可以直接线段树维护区间最大的负数,每次找到最大的负数,判断是否会变成正数或者0,然后暴力更新答案,这个的次数是$O(n)$的,所以复杂度$O(nlogn)$

#include<iostream>
#include<cstdio>
#include<vector>
#define MAXN 100000
#define INF 200000000000LL
#define ll long long
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

int n,m;
int s[MAXN+5],ans=0;
vector<int> v;
struct data
{
    ll x;int from;
    data operator + (data y)
    {
        return ((y.x<=0&&y.x>=x)||x>0)?y:*this;
    }
};
struct TREE{int l,r;ll val;data x;}T[MAXN*4+5];

void build(int x,int l,int r)
{
    if((T[x].l=l)==(T[x].r=r)){T[x].x=(data){s[l],l};return;}
    int mid=l+r>>1;
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
    T[x].x=T[x<<1].x+T[x<<1|1].x;
}

void pushdown(int x)
{
    int l=x<<1,r=x<<1|1;
    T[l].x.x+=T[x].val;T[r].x.x+=T[x].val;
    T[l].val+=T[x].val;T[r].val+=T[x].val;
    T[x].val=0;
}

void modify(int x,int l,int r,ll ad)
{
    if(T[x].l==l&&T[x].r==r)
    {
        if(T[x].x.x<=0&&T[x].x.x+ad>=0&&l!=r)
        {
            T[x].x.x+=ad;T[x].val+=ad;pushdown(x);
            int mid=l+r>>1;
            modify(x<<1,l,mid,ad);modify(x<<1|1,mid+1,r,ad);
            T[x].x=T[x<<1].x+T[x<<1|1].x;
            return;
        }
        T[x].val+=ad;T[x].x.x+=ad;
        return;
    }
    if(T[x].val)pushdown(x);
    int mid=T[x].l+T[x].r>>1;
    if(r<=mid)modify(x<<1,l,r,ad);
    else if(l>mid)modify(x<<1|1,l,r,ad);
    else {modify(x<<1,l,mid,ad);modify(x<<1|1,mid+1,r,ad);}
    T[x].x=T[x<<1].x+T[x<<1|1].x;
}

data query(int x,int l,int r)
{
    if(T[x].l==l&&r==T[x].r)return T[x].x;
    if(T[x].val)pushdown(x);int mid=T[x].l+T[x].r>>1;
    if(r<=mid) return query(x<<1,l,r);
    else if(l>mid)return query(x<<1|1,l,r);
    else return query(x<<1,l,mid)+query(x<<1|1,mid+1,r);
}

void calc(int x,int ad)
{
    if(x!=1)ans-=(query(1,x,x).x<0&&query(1,x-1,x-1).x>0);
    if(x!=n-1)ans-=(query(1,x,x).x>0&&query(1,x+1,x+1).x<0);
    if(x!=1)ans+=(query(1,x,x).x+ad<0&&query(1,x-1,x-1).x>0);
    if(x!=n-1)ans+=(query(1,x,x).x+ad>0&&query(1,x+1,x+1).x<0); 
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)s[i]=read();
    for(int i=1;i<n;i++)s[i]=s[i+1]-s[i];
    for(int i=1;++i<n;)ans+=(s[i-1]>0&&s[i]<0);
    build(1,1,n-1);printf("%d
",ans);
    for(int i=1;i<=m;i++)
    {
        int l=read(),r=read(),a=read(),d=read();
        if(l>1)calc(l-1,a),modify(1,l-1,l-1,a);
        if(r<n)calc(r,-(a+(r-l)*d)),modify(1,r,r,-(a+d*(r-l)));
        if(l<r)
        {    
            data now=query(1,l,r-1);
            while(l<r&&now.x<=0&&now.x+d>=0)
            {
                calc(now.from,d);
                modify(1,now.from,now.from,INF);
                v.push_back(now.from);
                now=query(1,l,r-1);
            }
            modify(1,l,r-1,d);
            for(int j=0;j<v.size();j++)
                modify(1,v[j],v[j],-INF);
            v.clear(); 
        }
        printf("%d
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/FallDream/p/liankao326.html