[WerKeyTom_FTD的模拟赛]Sone0

题目描述

有一颗n个节点的树,每个节点有编号与权值。有m次操作,每种操作都有独特的编号。
编号为1的操作,会切断当前树上存在的一条边,并新加一条边,保证操作完成后仍然是树。
编号为2的操作,会改变这颗树的根节点(初始根节点为1)。
编号为3的操作,会给树上一条路径上所有点的权值都增加x。
编号为4的操作,会对树上一条路径上点的权值信息进行轮换,如果是对j到k这条路径操作,从j走到k的遍历序列是a1~p。则a1的权值改为a2的权值,a2的权值改为a3的权值……ap-1的权值改为ap的权值,ap的权重改为a1的权值。
编号为5的操作,会对树上一条路径上所有点的权值都开方(下取整)
编号为6的操作,会询问树上一条路径上所有点的权值和。
编号为7的操作,会给定两个常数p和q。你需要求出两个正整数u和v,对于给定路径上任意两点权值x和y,需要满足 x/p<u/v<y/q 。如果无论如何都满足不了这个条件,那么需要满足给定路径上存在两点权值x和y,满足 p/x<v/u<q/y 。如果还是无论如何都不可能满足,那么该操作为非法操作。你需要求出u和v,并最小化v。对于非法操作输出-1。
编号为8的操作,会询问树中一个子树的大小。

数据范围

这里写图片描述

Task1

在一个区间上进行修改和询问,直接n^2暴力。

Task2

在一个区间上进行区间加法和区间求和,写棵线段树做到n log n。

Task3

在一个区间进行开方和求和操作。我们对于开方操作很棘手。但是可以注意到一个数被开方很少的次数后就会变成1,从此开方操作对它不产生影响。因此区间开方时,可以暴力递归左右。如果一个区间全都是1,则我们可以退出。这样是n log n的。

Task4

当同时有加法和开方时,Task3的做法不管用了。但我们可以用一样的思路去思考。其实,只要一个区间全都一样,我们就可以打赋值标记然后退出。这启示我们,每次线段树暴力递归修改的复杂度可以和颜色段数相关。那么,经过多少次开方后两个颜色段会合并呢?可以发现对于两个颜色段a和b,有(√a-√b)(√a+√b)=a-b,而√a-√b<√a+√b。我们每次会把差给开方!那么跟Task3一样,只需要很少的次数就能合并两个颜色段。
但这里有个Trick,因为本题的开方是下取整的。我们发现两个颜色段差为1时开方后的差仍可能为1,那么我们的算法就可能T掉。我们可以对最大值减最小值=1的区间也进行判断,开方后会变得一样就打赋值标记退出,否则打加法标记退出。
同时维护两种标记要注意标记顺序,通常是先赋值后加法,那么记得下传标记时也要按照这个顺序,而打一个赋值标记时也要记得清空加法标记。

Task5

我们仔细分析可以发现我们需要知道的只是最大值mx和最小值mi。
然后就是求 mx/p<u/v<mi/q
如果mx/p>mi/q,就是求 mi/q<u/v<mx/p
输出-1的情况是mx/p=mi/q。
在区间上进行最大值最小值的维护非常容易,我们来看怎么求u和v。
一个显然的结论是最小化分子或分母都是对的。
即不可能存在两组可能成为最优解的可行解u/v和u’/v’。 u<u 而v>v’。这样可以证明一定有一个可以在合法的情况下继续变小。
考虑问题求 a/b<u/v<c/d 的最小v和u。
如果a>=b,可以一直减到 a<b ,相当于减去一个整数把假分数化为真分数。
然后就是 a<b 的情况, a/b<u/v<c/d ,那么 d/c<v/u<b/a ,继续递归处理。
边界是a=0的时候。
可以发现上述算法就是一个类欧几里得算法,可以在log时间里求解。

Task6

序列上的轮换操作可以通过splay来实现,是n log n的。

Task7

在树上的暴力,有动态树的操作。我想到的是边集数组用map或set实现,就可以高效实现Link和cut。进行动态树操作后可以从根节点开始进行重构信息。
然而链表可以随便做。

Task8

在树上可以用树链剖分维护。

Task9

相对于Task8多了换根和求子树大小操作。
如果了解过ETT的人,可以比较清楚。没有link和cut操作下,即使有换根也是能维护子树大小的。我们并不需要真正的换根,只需要知道根节点是谁。比如我们以1为根,那么现在根节点是root,要求x的子树大小。如果root不在x的子树内,x的子树大小不变。否则,我们找到root的一个是x儿子的祖先z,x的子树大小应该是n-z的子树大小。其余部分和Task8一致。

Task10

当加入了link和cut操作后,我们就需要用动态树LCT进行维护了。

Task11

动态树的轮换问题比较难解决,为什么它不能像序列上一样容易呢?
因为动态树的splay,同时维护了权值和形态信息,轮换操作没有改变形态,只是改变了权值的对应关系,只用原先的splay无法进行维护。
那么我们可以把权值和形态信息进行分离,每条重链上保存两颗splay,一颗记录形态信息(即点的编号),一颗记录权值信息,两颗splay的中序遍历的权值和形态要一一对应。与权值相关的操作只需要在权值splay上进行操作(比如轮换),而与形态相关的操作通常需要两颗splay一起变动。这样常数虽然略大,但复杂度仍是n log n。

Task12

在有换根和link、cut情况下求子树大小,我们的LCT可以考虑记录虚边信息。
即对于每一个结点,保存siz表示所有连到它的虚子树大小之和,在虚实切换时进行更改即可。想知道一个点的子树大小,可以选择access它。
实现较麻烦。

Task13

基本是上面的杂合了。
值得一提的是开方操作,它和轮换、加法操作在树上也可以共存,仍然可以用颜色段解释,它的复杂度是正确的。在splay上抽取颜色段,也就相当于定位区间,而splay定位区间也是均摊log n的。不过为了保证它的复杂度,实现时可以一段一段抽出来,再合并到一起,这样才不会违背势能分析。不过其实也没有刻意卡,直接暴力递归左右大概也是可以过的。
严谨的解释为什么它的复杂度仍然是有保证的。对树进行轻重链剖分,一条重链可以看做一个序列。同序列上的证法一样,现在每次加法或轮换操作经过一条树路径,可以拆成两条自下而上的树路径,可以发现经过的重链数量是log级别,也相当于对log个序列进行了区间修改。
比较麻烦的是轻边的影响,因为一个点被修改,它连出的多条轻边都受到影响,这部分我们很难计算。但可以注意到每次开方操作也只经过log条轻边,可以直接把所有轻边连接的两个节点永久视为点权不一致,这样也没有关系。
这题是动态树,容易讨论一条边在一次link/cut操作前后的影响。如果都是重边或都是轻边没有关系,重边变成了轻边也没有关系,轻边变成重边也没有关系(可以证明变化量是log级别)。

参考程序

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=50000+10;
int tree[2][maxn][2],father[2][maxn],pp[2][maxn],sta[maxn],cal[2][maxn];
int size[2][maxn],siz[maxn],num[maxn];
ll mx[maxn],mi[maxn],sum[maxn],key[maxn],st[maxn],ad[maxn];
bool bz[2][maxn],fz[maxn];
int h[maxn],go[maxn*2],next[maxn*2];
int i,j,k,l,r,u,v,w,x,y,t,n,m,tot,top,root;
ll ans,da,xi,p,q;
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;
}
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void dfs(int x,int y){
    pp[0][x]=pp[1][x]=y;
    cal[0][x]=cal[1][x]=x;
    int t=h[x];
    while (t){
        if (go[t]!=y){
            dfs(go[t],x);
            siz[x]+=siz[go[t]]+1;
        }
        t=next[t];
    }
}
void update(int p,int x){
    size[p][x]=size[p][tree[p][x][0]]+size[p][tree[p][x][1]]+1;
    if (p==0){
        num[x]=num[tree[0][x][0]]+num[tree[0][x][1]]+siz[x];
    }
    else{
        mx[x]=key[x];
        if (tree[1][x][0]) mx[x]=max(mx[x],mx[tree[1][x][0]]);
        if (tree[1][x][1]) mx[x]=max(mx[x],mx[tree[1][x][1]]);
        mi[x]=key[x];
        if (tree[1][x][0]) mi[x]=min(mi[x],mi[tree[1][x][0]]);
        if (tree[1][x][1]) mi[x]=min(mi[x],mi[tree[1][x][1]]);
        sum[x]=sum[tree[1][x][0]]+sum[tree[1][x][1]]+key[x];
    }
}
int pd(int p,int x){
    return tree[p][father[p][x]][1]==x;
}
void rotate(int p,int x){
    int y=father[p][x],z=pd(p,x);
    father[p][x]=father[p][y];
    if (father[p][y]) tree[p][father[p][y]][pd(p,y)]=x;
    tree[p][y][z]=tree[p][x][1-z];
    if (tree[p][x][1-z]) father[p][tree[p][x][1-z]]=y;
    tree[p][x][1-z]=y;
    father[p][y]=x;
    update(p,y);
    update(p,x);
    if (pp[p][y]) pp[p][x]=pp[p][y],pp[p][y]=0;
    if (cal[p][y]) cal[p][x]=cal[p][y],cal[p][y]=0;
}
void markbz(int p,int x){
    swap(tree[p][x][0],tree[p][x][1]);
    bz[p][x]^=1;
}
void markst(int x,ll v){
    fz[x]=1;
    st[x]=mx[x]=mi[x]=key[x]=v;
    sum[x]=(ll)v*size[1][x];
    ad[x]=0;
}
void markad(int x,ll v){
    sum[x]+=(ll)v*size[1][x];
    ad[x]+=v;mx[x]+=v;mi[x]+=v;key[x]+=v;
}
void clear(int p,int x){
    if (bz[p][x]){
        if (tree[p][x][0]) markbz(p,tree[p][x][0]);
        if (tree[p][x][1]) markbz(p,tree[p][x][1]);
        bz[p][x]=0;
    }
    if (p==1){
        if (fz[x]){
            if (tree[1][x][0]) markst(tree[1][x][0],st[x]);
            if (tree[1][x][1]) markst(tree[1][x][1],st[x]);
            fz[x]=0;
        }
        if (ad[x]){
            if (tree[1][x][0]) markad(tree[1][x][0],ad[x]);
            if (tree[1][x][1]) markad(tree[1][x][1],ad[x]);
            ad[x]=0;
        }
    }
}
void remove(int p,int x,int y){
    top=0;
    while (x!=y){
        sta[++top]=x;
        x=father[p][x];
    }
    while (top) clear(p,sta[top--]);
}
void splay(int p,int x,int y){
    remove(p,x,y);
    while (father[p][x]!=y){
        if (father[p][father[p][x]]!=y)
            if (pd(p,x)==pd(p,father[p][x])) rotate(p,father[p][x]);else rotate(p,x);
        rotate(p,x);
    }
}
int kth(int p,int x,int y){
    if (!x) return 0;
    if (size[p][tree[p][x][0]]+1==y) return x;
    clear(p,x);
    if (size[p][tree[p][x][0]]+1>y) return kth(p,tree[p][x][0],y);
    else return kth(p,tree[p][x][1],y-size[p][tree[p][x][0]]-1);
}
int findfr(int p,int x){
    splay(p,x,0);
    int k=size[p][tree[p][x][0]]+1;
    int u=cal[p][x];
    splay(1-p,u,0);
    int v=kth(1-p,u,k);
    splay(1-p,v,0);
    return v;
}
int getsize(int x){
    splay(0,x,0);
    return siz[x]+num[tree[0][x][1]]+size[0][tree[0][x][1]]+1;
}
void real_empty(int p,int x,int y){
    if (p==0){
        splay(p,y,0);
        siz[y]+=getsize(x);
        update(0,y);
    }
    splay(p,y,0);
    splay(p,x,y);
    tree[p][y][1]=0;
    father[p][x]=0;
    pp[p][x]=y;
    update(p,y);
}
void empty_real(int p,int x,int y){
    if (p==0){
        splay(p,y,0);
        siz[y]-=getsize(x);
        update(0,y);
    }
    splay(p,y,0);
    splay(p,x,0);
    cal[p][x]=0;
    tree[p][y][1]=x;
    father[p][x]=y;
    pp[p][x]=0;
    update(p,y);
}
void access(int x){
    int j,k,y,z,u,v,w;
    splay(0,x,0);
    z=kth(0,tree[0][x][1],1);
    if (z){
        splay(0,z,x);
        v=findfr(0,x);w=findfr(0,z);
        real_empty(0,z,x);real_empty(1,w,v);
        splay(0,z,0);splay(1,w,0);
        cal[0][z]=w;cal[1][w]=z;
        splay(0,x,0);splay(1,v,0);
        cal[0][x]=v;cal[1][v]=x;
    }
    while (pp[0][x]){
        y=pp[0][x];
        splay(0,y,0);
        z=kth(0,tree[0][y][1],1);
        if (z){
            splay(0,z,y);
            v=findfr(0,y);w=findfr(0,z);
            real_empty(0,z,y);real_empty(1,w,v);
            splay(0,z,0);splay(1,w,0);
            cal[0][z]=w;cal[1][w]=z;
            splay(0,y,0);splay(1,v,0);
            cal[0][y]=v;cal[1][v]=y;
        }
        splay(0,x,0);
        z=kth(0,x,1);
        splay(0,z,0);
        v=findfr(0,y);w=findfr(0,z);
        empty_real(0,z,y);empty_real(1,w,v);
        splay(0,x,0);
    }
}
void makeroot(int x){
    access(x);
    splay(0,x,0);
    markbz(0,x);
    int u=cal[0][x];
    splay(1,u,0);
    markbz(1,u);
}
void cut(int x,int y){
    makeroot(x);
    access(x);
    splay(0,x,0);
    siz[x]-=getsize(y);
    update(0,x);
    splay(0,y,0);
    pp[0][y]=0;
    int u=cal[0][y];
    splay(1,u,0);
    pp[1][u]=0;
}
void link(int x,int y){
    makeroot(x);
    makeroot(y);
    access(x);
    splay(0,x,0);
    siz[x]+=getsize(y);
    update(0,x);
    splay(0,y,0);
    pp[0][y]=x;
    int u=cal[0][y],v=cal[0][x];
    splay(1,u,0);
    pp[1][u]=v;
}
void kf(int x,int y){
    if (!x) return;
    if (y>t){
        t=y;
        v=x;
    }
    clear(1,x);
    if (mx[x]==mi[x]){
        markst(x,int(sqrt(mx[x])));
        return;
    }
    else if (mx[x]==mi[x]+1){
        if (int(sqrt(mx[x]))==int(sqrt(mi[x]))) markst(x,int(sqrt(mx[x])));
        else markad(x,int(sqrt(mi[x]))-mi[x]);
        return;
    }
    key[x]=int(sqrt(key[x]));
    kf(tree[1][x][0],y+1);kf(tree[1][x][1],y+1);
    update(1,x);
}
/*void kf(int x,int y){
    if (!x) return;
    if (y>t){
        t=y;
        v=x;
    }
    clear(1,x);
    key[x]=int(sqrt(key[x]));
    kf(tree[1][x][0],y+1);kf(tree[1][x][1],y+1);
    update(1,x);
}*/
void likegcd(int a,int b,int c,int d){
    if (a==0){
        q=1;p=floor(d/c)+1;
        return;
    }
    if (a>=b){
        likegcd(a%b,b,c-d*(a/b),d);
        q+=(ll)p*(a/b);
        return;
    }
    if (c>d){
        p=q=1;
        return;
    }
    likegcd(d,c,b,a);
    swap(p,q);
}
void write(ll x){
    if (x<0){
        putchar('-');
        x=-x;
    }
    if (!x){
        putchar('0');
        return;
    }
    top=0;
    while (x){
        sta[++top]=x%10;
        x/=10;
    }
    while (top) putchar('0'+sta[top--]);
}
int main(){
    freopen("satori.in","r",stdin);freopen("satori.out","w",stdout);
    n=read();m=read();
    fo(i,1,n-1){
        j=read();k=read();
        add(j,k);add(k,j);
    }
    dfs(1,0);
    fo(i,1,n){
        key[i]=read();
        update(0,i);update(1,i);
    }
    root=1;
    while (m--){
        t=read();
        if (t==1){
            j=read();k=read();u=read();v=read();
            cut(j,k);
            link(u,v);
        }
        else if (t==2){
            j=read();
            root=j;
        }
        else if (t==3){
            j=read();k=read();x=read();
            makeroot(j);
            access(k);
            splay(0,k,0);
            u=cal[0][k];
            splay(1,u,0);
            markad(u,x);
        }
        else if (t==4){
            j=read();k=read();
            makeroot(j);
            access(k);
            splay(0,k,0);
            u=cal[0][k];
            splay(1,u,0);
            v=kth(1,u,1);
            splay(1,v,0);
            w=tree[1][v][1];
            if (w){
                father[1][w]=0;
                tree[1][v][1]=0;
                update(1,v);
                x=kth(1,w,size[1][w]);
                splay(1,x,0);
                tree[1][x][1]=v;
                father[1][v]=x;
                update(1,x);
            }
        }
        else if (t==5){
            j=read();k=read();
            makeroot(j);
            access(k);
            splay(0,k,0);
            u=cal[0][k];
            splay(1,u,0);
            t=-1;
            kf(u,0);
            splay(1,v,0);
        }
        else if (t==6){
            j=read();k=read();
            makeroot(j);
            access(k);
            splay(0,k,0);
            u=cal[0][k];
            splay(1,u,0);
            ans=sum[u];
            write(ans);putchar('\n');
        }
        else if (t==7){
            j=read();k=read();p=read();q=read();
            makeroot(j);
            access(k);
            splay(0,k,0);
            u=cal[0][k];
            splay(1,u,0);
            da=mx[u];xi=mi[u];
            if ((ll)da*q>(ll)xi*p){
                swap(da,xi);
                swap(p,q);
            }
            if ((ll)da*q==(ll)xi*p){
                write(-1);putchar('\n');
                continue;
            }
            likegcd(da,p,xi,q);
            write(q);putchar(' ');write(p);putchar('\n');
        }
        else if (t==8){
            j=read();
            makeroot(root);
            access(j);
            ans=siz[j]+1;
            write(ans);putchar('\n');
        }
    }
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值