樹的dfs序,p1539,p1651,,2018/11/08模擬賽T3

  樹的歐拉序指從根節點進行dfs(先序遍歷),每次到達某個點的時間和離開這個點的時間.它可以將樹上的問題轉換成序列問題進行處理. 

  比如對於p1539的樣例可以這樣解釋.

  

  每個點的左邊數字表示進入該點的"時間",右邊的數字表示離開該點的"時間".對歐拉序的介紹就到這裏.

  然後來看一個例題:

  

  

  先讀入邊,跑一遍dfs確定歐拉序.

  對於操作1,把點x的進入的"時間"+=a,把x出去的"時間"-=a

  這樣操作3詢問根節點到y的路徑點權和時,我們詢問y的進入的時間的前綴和.如果這個路徑經過點x,即x是y的祖先,由於x的進入的時間早於y進入的時間,x出去的時間晚於y進入的時間,貢獻會正確的加上去,否則1.如果歐拉序本來就在y之後當然不會影響.2.如果x的歐拉序在y之前,一定進入的時間和出去的時間都早於y進入的時間,對y沒有貢獻.

  如果只有操作1,3,只需要一個樹狀數組即可.那來看一下操作2.將x的子樹所有節點點權都+v,這個就有些難了吧.

  考慮區間修改,用一個線段樹維護區間點權和與區間內"進入的數量-出去的數量",每次修改的時候將x進入的時間和出去的時間這個區間打上標記v.下傳標記的時候可以使得區間和+=v*"進去的數量-出去的數量".

  至此,本題已經A掉了.很久沒寫區間修改的線段樹了,竟然不知道區間修改需要懶標記...寫代碼20分鐘,找錯誤30分鐘,發現懶標記的問題後5分鐘修改完畢.

   

  並且提交的時候出現了上述四種情況:數組開小(歐拉序需要2n,那麼線段樹需要8n),沒關freopen,沒開long long,A掉.真是菜的真實.

long long i,tx,ty,t[100010],tv,tl,tr,flag,ttt,T,jia[800010];
long long n,m,head[100010],tot;
struct edge
{
    long long x,y,next;
}e[200010];
struct node
{
    long long l,r;
}o[100010];
long long c[800010],sum[800010];
void bian(long long x,long long y)
{
    tot++;
    e[tot].x=x;
    e[tot].y=y;
    e[tot].next=head[x];
    head[x]=tot;
}
void dfs(long long x)
{
    tot++;
    o[x].l=tot;
    for(long long j=head[x];j;j=e[j].next)
    {
        if(!o[e[j].y].l)
        {
            dfs(e[j].y);
        }
    }
    tot++;
    o[x].r=tot;
}
void build(long long now,long long l,long long r)
{
    if(flag)
        sum[now]++;
    else 
        sum[now]--;
    if(l==r)
        return;
    long long mid=(l+r)>>1;
    if(tx<=mid)build(now<<1,l,mid);
    else       build(now<<1|1,mid+1,r);
}

void pushdown(long long now)
{
    jia[now<<1]+=jia[now];
    jia[now<<1|1]+=jia[now];
    c[now<<1]+=sum[now<<1]*jia[now];
    c[now<<1|1]+=sum[now<<1|1]*jia[now];
    jia[now]=0;
}
void add(long long now,long long l,long long r)
{
    c[now]+=tv;
    if(l==r)
        return ;
    if(jia[now])
        pushdown(now);
    long long mid=(l+r)>>1;
    if(ttt<=mid)add(now<<1,l,mid);
    else       add(now<<1|1,mid+1,r);
}
void qujian(long long now,long long l,long long r)
{
    if(tl<=l&&r<=tr)
    {
        c[now]+=sum[now]*tv;
        jia[now]+=tv;
        return ;
    }
    long long mid=(l+r)/2;
    if(jia[now])
        pushdown(now);
    if(tl<=mid)qujian(now<<1,l,mid);
    if(tr>mid)qujian(now<<1|1,mid+1,r);
    c[now]=c[now<<1]+c[now<<1|1];
}
long long ask(long long now,long long l,long long r)
{
    if(1<=l&&r<=tx)
        return c[now];
    long long mid=(l+r)/2;
    long long tsum=0;
    if(jia[now])
        pushdown(now);
    if(1<=mid)tsum+=ask(now<<1,l,mid);
    if(tx>mid)tsum+=ask(now<<1|1,mid+1,r);
    return tsum;
}
void check(long long now,long long l,long long r)
{
    cout<<l<<' '<<r<<' '<<sum[now]<<' '<<c[now]<<endl;
    if(l==r)
        return ;
    check(now<<1,l,(l+r)/2);
    check(now<<1|1,(l+r)/2+1,r);
}
int main()
{

int __size__ = 20 << 20; // 20MB
char *__p__ = (char*)malloc(__size__) + __size__;
__asm__("movl %0, %%esp\n" :: "r"(__p__));
    n=read();m=read();
    for(i=1;i<=n;i++)
        t[i]=read();
    for(i=1;i<n;i++)
    {
        tx=read();ty=read();
        bian(tx,ty);
        bian(ty,tx);
    }
    tot=0;
    dfs(1);
    for(i=1;i<=n;i++)
    {
        tx=o[i].l;
        flag=1;
        build(1,1,tot);
        tx=o[i].r;
        flag=0;
        build(1,1,tot);
        tv=t[i];
        ttt=o[i].l;
        add(1,1,tot);
        ttt=o[i].r;tv=-t[i];
        add(1,1,tot);
    }
    for(i=1;i<=m;i++)
    {
        T=read();
        if(T==1)
        {
            tx=read();tv=read();
            ttt=o[tx].l;
            add(1,1,tot);
            ttt=o[tx].r;tv=-tv;
            add(1,1,tot);
        }
        else if(T==2)
        {
            tx=read();tv=read();
            tl=o[tx].l;tr=o[tx].r;
            qujian(1,1,tot);
        }
        else //if(t==3)
        {
            tx=o[read()].l;
            write(ask(1,1,tot));
            putchar(10);
        }
    }
    //check(1,1,tot);
}
隨便寫個什麼題都200行代碼啊

  

  

  你需要支持子樹修改,單點查詢.我們又可以上歐拉序.那個"初始工資"看似要寫一個單點修改,但是隻要事先記下來,以後裝作不存在,詢問的時候加上去即可.

  仔細思考一下後想到區間修改單點查詢完全可以上一個樹狀數組嘛.於是一個好想好實現的代碼就完成了.

  

long long t[500010],head[500010],tot,c[1000010];
struct edge
{
    long long x,y,next;
}e[2000010];
struct node
{
    long long l,r;
}o[500010];
inline void dfs(long long x)
{
    tot++;
    o[x].l=tot;
    for(long long j=head[x];j;j=e[j].next)
        if(!o[e[j].y].l)
            dfs(e[j].y);
    tot++;
    o[x].r=tot;
}
inline long long lowbit(long long x)
{
    return x&(-x);
}
inline void add(long long x,long long v)
{
    for(;x<=tot;x+=lowbit(x))
        c[x]+=v;
}
inline long long ask(long long x)
{
    long long sum=0;
    for(;x;x-=lowbit(x))
        sum+=c[x];
    return sum;
}    
int main()
{
    long long n=read(),m=read();
    t[1]=read();
    for(int i=2;i<=n;i++)
    {
        t[i]=read();
        long long ty=read();
        e[++tot]=(edge){i,ty,head[i]};
        head[i]=tot;
        e[++tot]=(edge){ty,i,head[ty]};
        head[ty]=tot;
    }
    tot=0;
    dfs(1);
    for(;m;m--)
    {
        char op=getc();
        while(op!='p'&&op!='u')
            op=getc();
        if(op=='p')
        {
            long long tx=read();long long tv=read();
            if(o[tx].l+1!=o[tx].r)
            {
                add(o[tx].l+1,tv);
                add(o[tx].r-1,-tv);
            }
        }
        else
        {
            long long xx=read();
            write(ask(o[xx].l)+t[xx]);
            putchar(10);
        }
    }
}
View Code

 

  再看一道題:
  2018年11月8號NOIP前的最後一場模擬賽T3.

  

  

輸入樣例
5
1 2
2 3
3 4
4 5
4
1 3 5 2
2
1 5
0

輸出樣例
Yes
1 2
3 5
Yes
1 5

 

 

 

   理解題意後,腦補一下可以得到一定可以構造出<=n的解.因爲我們把所有點按dfs序排一下序,第一次a1a2,a3a4,a5a6...an-1an分配,第二次按a2a3,a4a5...ana1分配,最壞也不過是覆蓋樹上所有邊,總的距離<=2(n-1),又有抽屜原理,更短的那一個總是要<=n-1的.

  我們dfs跑出dfs序,按dfs序sort一下,兩次分配用lca算一下總的距離,輸出小的即可.

  慶祝AK啦啦啦(~ ̄▽ ̄)~

  

 

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