[BZOJ3091]城市旅行

壹、题目描述

传送门 to LUOGU

贰、一些思考

目测 \(\tt LCT\) .

对于一条路径,设点排下来是 \(p_1,p_2,p_3,...,p_k\),那么这条路径的 \(E\) 的值即为

\[\sum_{i=1}^kp_i\times i\times (k-i+1)\over {k\choose 2} \]

为了简便表达,我们将 \(v_{p_i}\) 直接写成 \(p_i\).

对于一个点,我们肯定得维护答案,考虑当前根为 \(t\),那么 \(t\) 中一定需要维护上面那个值的分子,我们记这个东西为 \(\rm exp\),但是在由子到父亲上传的时候,对于 \(t\) 的左子树,在它的根中,维护的比我们想要的要少一些,少的部分其实就是左子树的点和右子树匹配的情况,对于左子树我们单独再储存一个 \(1\times p_1+2\times p_2+...+(t-1)\times p_{t-1}\),记这个变量为 \(\rm lsum\),那么少的部分就是 \(\text{lsum}\times (\text{siz}_r+1)\),这是左边的点和右边匹配的情况,同样,我们便也需要存下一个 \(\text{rsum}=1\times p_k+2\times p_{k-1}+....\) 为右子树的情况,上传同理;

考虑我们接下来需要维护的变量

  • \(\rm siz\),不必多说;
  • \(\rm val\),就是这个点的点值;
  • \(\exp\),起到类似于储存答案的功效;
  • \(\rm lsum,rsum\),上传时有奇效;
  • \(\rm sum\),处理 \(\rm lsum,rsum\) 时需要使用,表示子树内所有点的 \(\rm val\) 之和;

但是还有一个操作 —— 路径权值修改。对于 \(\rm lsum,rsum\) 的修改都比较简单,有 \(\Delta=x\times (1+2+3+...+k)=x{k(k+1)\over 2}\). 修改的难点主要在于 \(\rm exp\) 的修改上,对于它,我们不妨将式子写出来:

\[\begin{aligned} Ans&=\sum_{i=1}^k(p_i+\Delta)\times i\times (k-i+1) \\ &=\sum_{i=1}^kp_i\times i\times (k-i+1)+\Delta\sum_{i=1}^ki\times (k-i+1) \\ &=\exp+\Delta\times {k(k+1)(k+2)\over 6} \end{aligned} \]

最后一步的推导直接展开合并写成通项即可。

所以对于 \(\exp\) 的修改也可以直接做了,只要我们有 \(k\)\(\rm siz\) .

然后,就切掉了?剩下的,就是如何用代码实现这个过程了。

时间复杂度 \(\mathcal O(n\log n)\).

叁、参考代码

\(\color{red}{\text{talk is LCT, show you the code.}}\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define mp(a, b) make_pair(a, b)

typedef long long ll;
template<class T>inline T readin(T x){
    x=0; int f=0; char c;
    while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}

const int maxn=5e4;

ll gcd(ll x, ll y){
    return !y? x: gcd(y, x%y);
}

int n, m;

namespace lct{
    int son[maxn+5][2], fa[maxn+5];
    int siz[maxn+5], rev[maxn+5];
    ll tag[maxn+5], lsum[maxn+5], rsum[maxn+5], exp[maxn+5], sum[maxn+5], val[maxn+5];
    inline int isroot(int x){
        return son[fa[x]][0]!=x && son[fa[x]][1]!=x;
    }
    inline void reverse(int x){
        if(!x) return;
        swap(son[x][0], son[x][1]);
        swap(lsum[x], rsum[x]);
        rev[x]^=1;
    }
    inline void add(int x, ll delta){
        if(!x) return;
        tag[x]+=delta, val[x]+=delta;
        lsum[x]+=delta*siz[x]*(siz[x]+1)/2;
        rsum[x]+=delta*siz[x]*(siz[x]+1)/2;
        sum[x]+=delta*siz[x];
        exp[x]+=delta*siz[x]*(siz[x]+1)*(siz[x]+2)/6;
    }
    inline void pushup(int x){
        int ls=son[x][0], rs=son[x][1];
        siz[x]=siz[ls]+siz[rs]+1;
        sum[x]=sum[ls]+sum[rs]+val[x];
        lsum[x]=lsum[ls]+(siz[ls]+1)*(val[x]+sum[rs])+lsum[rs];
        rsum[x]=rsum[rs]+(siz[rs]+1)*(val[x]+sum[ls])+rsum[ls];
        exp[x]=exp[ls]+exp[rs]+lsum[ls]*(siz[rs]+1)+rsum[rs]*(siz[ls]+1)+val[x]*(siz[ls]+1)*(siz[rs]+1);
    }
    inline void pushdown(int x){
        if(tag[x]) add(son[x][0], tag[x]), add(son[x][1], tag[x]);
        if(rev[x]) reverse(son[x][0]), reverse(son[x][1]);
        tag[x]=rev[x]=0;
    }
    inline void connect(int x, int y, int d){
        son[x][d]=y, fa[y]=x;
    }
    inline void rotate(int x){
        int y=fa[x], z=fa[y], d=(son[y][1]==x);
        fa[x]=z; if(!isroot(y)) son[z][son[z][1]==y]=x;
        connect(y, son[x][d^1], d);
        connect(x, y, d^1);
        pushup(y), pushup(x);
    }
    inline void splay(int x){
        static int sta[maxn+5], ed=0; int now=x;
        while(sta[++ed]=now, !isroot(now)) now=fa[now];
        while(ed) pushdown(sta[ed--]);
        int y, z;
        while(!isroot(x)){
            y=fa[x], z=fa[y];
            if(!isroot(y))
                (son[z][1]==y)^(son[y][1]==x)? rotate(y): rotate(x);
            rotate(x);
        }
    }
    inline void access(int x){
        for(int pre=0; x; pre=x, x=fa[x])
            splay(x), son[x][1]=pre, pushup(x);
    }
    inline int findrt(int x){
        access(x); splay(x); pushdown(x);
        while(son[x][0]) pushdown(x=son[x][0]);
        return splay(x), x;
    }
    inline void makert(int x){
        access(x); splay(x); reverse(x);
    }
    inline void link(int x, int y){
        makert(x);
        if(findrt(y)==x) return;
        fa[x]=y;
    }
    inline void cut(int x, int y){
        makert(x);
        if(findrt(y)==x && fa[y]==x && son[y][0]==0)
            son[x][1]=fa[y]=0, pushup(x);
    }
    inline void alladd(int x, int y, ll delta){
        makert(x);
        if(findrt(y)!=x) return;
        access(y); splay(y);
        add(y, delta);
    }
    inline pair<ll, int> query(int x, int y){
        makert(x);
        if(findrt(y)!=x) return mp(-1, -1);
        access(y); splay(y);
        return mp(exp[y], siz[y]);
    }
    inline void print(){
        for(int i=1; i<=n; ++i){
            printf("node %d :>\n", i);
            printf("fa == %d\n", fa[i]);
            printf("ls == %d, rs == %d\n", son[i][0], son[i][1]);
            printf("val == %lld, siz == %d\n", val[i], siz[i]);
            printf("sum == %lld, exp == %lld\n", sum[i], exp[i]);
            puts("------------------------------");
        }
    }
    inline void initial(){
        for(int i=1; i<=n; ++i){
            siz[i]=1, lsum[i]=rsum[i]=exp[i]=sum[i]=val[i]=readin(1);
        }
        int u, v;
        for(int i=1; i<n; ++i){
            u=readin(1), v=readin(1);
            link(u, v);
        }
    }
}

inline void input(){
    n=readin(1), m=readin(1);
    lct::initial();
}

signed main(){
    input();
    int cmd, x, y, d;
    while(m--){
        cmd=readin(1), x=readin(1), y=readin(1);
        if(cmd==1) lct::cut(x, y);
        else if(cmd==2) lct::link(x, y);
        else if(cmd==3){
            d=readin(1);
            lct::alladd(x, y, d);
        }
        else if(cmd==4){
            pair<ll, int>ret=lct::query(x, y);
            ll up=ret.first, down=ret.second;
            if(up==-1){ printf("-1\n"); continue; }
            down=down*(down+1)/2;
            ll d=gcd(up, down);
            printf("%lld/%lld\n", up/d, down/d);
        }
    }
    return 0;
}

注意执行 reverse 的时候也要把 \(\rm lsum,rsum\) 进行交换。

肆、用到の小 \(\tt trick\)

本题计算期望的方法是 \(\cfrac{\text{整体}}{\text{整体}}\),考虑每个点被算进多少次路径,得到这个点对期望的总价值贡献,求和,以方案数除之,是答案也。

另外,对于这种题,似乎有时可以从询问方面得到我们需要维护什么,对于修改时,再考虑怎么维护这些值即可。

但是可能也会出现我们一开始无法直接维护答案,因为不是所有的答案都可以直接维护,所以有时可以将答案拆成多个部分,分开进行维护,然后拼接起来,比如此题并未将 \(k\choose 2\) 放入维护中,为之高次,亦于分母耶,是大繁也。

posted @ 2021-03-27 17:35  Arextre  阅读(54)  评论(0编辑  收藏  举报