【bzoj3589】動態樹 樹鏈剖分+線段樹

題目大意:
給定一棵樹,支持以下兩個操作:
0、子樹修改
1、查詢幾條的鏈並的權值和,答案模2^31。
(這些鏈爲某個節點到根的路徑的一部分)

題目分析:(樹鏈剖分+線段樹)
首先,這道題跟動態樹沒什麼關係。
其他的題解有用什麼容斥原理,還有奇怪的動態樹做的,我覺得樹鏈剖分+線段樹就足夠了。

我們假設不是求這些鏈的並,而是求這些鏈的權值和。
那麼只需要樹鏈剖分之後維護線段樹就可以了。

那麼剩下的問題就是如何求這些鏈的並。

無交的鏈自然不用處理。
由於鏈的奇怪性質,有交的鏈必然是類似於下圖的樣子:
這裏寫圖片描述
這樣的鏈在兩個最低點的lca處以上的部分相當於公共部分,所以我們完全可以轉化成兩個這樣的鏈:
這裏寫圖片描述

就是把有並的兩條鏈拆成覆蓋原來兩條鏈的所有點的兩條不想交的鏈。

這個我們可以枚舉兩條鏈來拆。
具體可以這樣做:
求兩個鏈底的LCA
如果這個LCA的深度低於任意一個鏈頂,則證明這兩條鏈沒有交。
否就把一個條鏈的鏈頂設爲兩個鏈頂中深度較淺的那個,另一條鏈的鏈頂設爲LCA的下一個(詳見代碼)。

注意:
在枚舉兩條鏈的時候,我們相當於往原來的鏈的集合中新加一個,在與其他鏈進行合併的時候,要使當前新加進來的鏈能包含所有新加進來的點,所以要每次把原來的鏈與LCA斷開。

代碼如下:

#include <cstdio>
#include <algorithm>
#include <iostream>
#define N 200001
#define ls(c) (c<<1)
#define rs(c) (c<<1|1)
using namespace std;
struct segment{
    int l,r;
    int sum,mark;
}seg[N<<2];
int n,m,opt,k,x,y;
int a[8],b[8];
int fir[N],nes[N<<1],v[N<<1],tot=1;
int fa[N],dep[N],zon[N],sz[N],pos[N],ld[N],top;
void edge(int x,int y)
{
    v[++tot]=y;
    nes[tot]=fir[x];
    fir[x]=tot;
    return;
}
#define edge(x,y) edge(x,y),edge(y,x)
void dfs1(int c)
{
    sz[c]=1;
    dep[c]=dep[fa[c]]+1;
    for(int t=fir[c];t;t=nes[t])
    {
        if(v[t]==fa[c]) continue;
        fa[v[t]]=c;
        dfs1(v[t]);
        sz[c]+=sz[v[t]];
        if(sz[v[t]]>sz[zon[c]]) zon[c]=v[t];
    }
}
void dfs2(int c)
{
    pos[c]=++top;
    ld[c]=c;
    if(zon[fa[c]]==c) ld[c]=ld[fa[c]];
    if(zon[c]) dfs2(zon[c]);
    for(int t=fir[c];t;t=nes[t])
    {
        if(v[t]==fa[c] || v[t]==zon[c]) continue;
        dfs2(v[t]);
    }
}
int lca(int x,int y)
{
    while(ld[x]!=ld[y])
    {
        if(dep[ld[x]]<dep[ld[y]]) swap(x,y);
        x=fa[ld[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    return y;
}
void add_mark(int c,int v)
{
    seg[c].sum+=(seg[c].r-seg[c].l+1)*v;
    seg[c].mark+=v;
}
void push_down(int c)
{
    if(seg[c].l!=seg[c].r)
    {
        add_mark(ls(c),seg[c].mark);
        add_mark(rs(c),seg[c].mark);
    }
    seg[c].mark=0;
}
void update(int c,int l,int r,int y)
{
    if(l<=seg[c].l && r>=seg[c].r)
    {
        add_mark(c,y);
        return;
    }
    push_down(c);
    int mid=seg[c].l+seg[c].r>>1;
    if(l<=mid) update(ls(c),l,r,y);
    if(r>mid)  update(rs(c),l,r,y);
    seg[c].sum=seg[ls(c)].sum+seg[rs(c)].sum;
}
int query(int c,int l,int r)
{
    push_down(c);
    if(l<=seg[c].l && r>=seg[c].r) return seg[c].sum;
    int mid=seg[c].l+seg[c].r>>1;
    if(r<=mid) return query(ls(c),l,r);
    if(l>mid)  return query(rs(c),l,r);
    return query(ls(c),l,r)+query(rs(c),l,r);
}
void build_tree(int c,int l,int r)
{
    seg[c].l=l,seg[c].r=r,seg[c].sum=seg[c].mark=0;
    if(l==r) return;
    int mid=l+r>>1;
    build_tree(ls(c),l,mid);
    build_tree(rs(c),mid+1,r);
}
void query()
{
    int ans=0;
    for(int i=1;i<=k;i++)
    {
        if(dep[a[i]]>dep[b[i]]) swap(a[i],b[i]);
        for(int j=1;j<i;j++)
        {
            if(a[j]==0 || b[j]==0) continue;
            int LCA=lca(b[i],b[j]);
            if(dep[LCA]<dep[a[j]] || dep[LCA]<dep[a[i]]) continue;
            a[i]=dep[a[i]]<dep[a[j]]?a[i]:a[j];
            if(b[j]==LCA) {a[j]=b[j]=0;continue;}
            a[j]=b[j];
            while(ld[a[j]]!=ld[LCA])
            {
                if(fa[ld[a[j]]]==LCA)
                {
                    a[j]=ld[a[j]];
                    break;
                }
                a[j]=fa[ld[a[j]]];
            }
            if(ld[a[j]]==ld[LCA]) a[j]=zon[LCA];
        }
    }
    for(int i=1;i<=k;i++)
    {
        if(a[i]==0 || b[i]==0) continue;
        while(ld[a[i]]!=ld[b[i]])
        {
            ans+=query(1,pos[ld[b[i]]],pos[b[i]]);
            b[i]=fa[ld[b[i]]];
        }
        ans+=query(1,pos[a[i]],pos[b[i]]);
    }
    printf("%d\n",ans&(0x7fffffff));
}
int main()
{
    scanf("%d",&n);
    for(int i=1,x,y;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        edge(x,y);
    }
    dfs1(1);dfs2(1);
    build_tree(1,1,n);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d",&opt);
        switch(opt)
        {
            case 0:
                scanf("%d%d",&x,&y);
                update(1,pos[x],pos[x]+sz[x]-1,y);
                break;
            case 1:
                scanf("%d",&k);
                for(int i=1;i<=k;i++)
                    scanf("%d%d",&a[i],&b[i]);
                query();
                break;
        }
    }
    return 0;
}

附帶一個小數據生成器:

#include <cstdio>
#include <ctime>
#include <algorithm>
#include <iostream>
#define N 120000
using namespace std;
int n,m;
int f[N];
int fa[N],dep[N];
int fir[N],nes[N<<1],v[N<<1],tot=1;
void edge(int x,int y)
{
    v[++tot]=y;
    nes[tot]=fir[x];
    fir[x]=tot;
    return;
}
#define edge(x,y) edge(x,y),edge(y,x)
int findie(int c)
{
    if(f[c]!=c) f[c]=findie(f[c]);
    return f[c];
}
void dfs(int c)
{
    dep[c]=dep[fa[c]]+1;
    for(int t=fir[c];t;t=nes[t])
    {
        if(fa[c]==v[t]) continue;
        fa[v[t]]=c;
        dfs(v[t]);
    }
}
int main()
{
    freopen("input.in","w",stdout);
    srand(time(0));
    n=10;
    m=20;
    cout<<n<<endl;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1,x,y;i<n;i++)
    {
        x=rand()%n+1;y=rand()%n+1;
        while(findie(x)==findie(y)) x=rand()%n+1,y=rand()%n+1;
        f[findie(x)]=findie(y);
        edge(x,y);
        cout<<x<<" "<<y<<endl;
    }
    dfs(1);
    cout<<m<<endl;
    for(int i=1,x,y,k,t;i<=m;i++)
    {
        int opt=rand()%2;
        switch(opt)
        {
        case 0:
            cout<<opt<<" "<<rand()%n+1<<" "<<rand()%10<<endl;
            break;
        case 1:
            k=rand()%5+1;
            cout<<opt<<" "<<k<<" ";
            for(int j=1;j<=k;j++)
            {
                x=rand()%n+1;
                t=rand()%dep[x];
                y=x;
                while(t--) y=fa[y];
                cout<<x<<" "<<y<<" ";
            }
            cout<<endl;
            break;
        }
    }

    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章