[BZOJ 3631] 松鼠的新家

Link:

BZOJ 3631 传送门

 

Solution:

这题一眼看上去是裸的树剖,但实际上完全没有必要进行区间加的操作

 

由于不需要在线的查询,我们可以直接用差分数组来解决此题

而这又有两种方式:

1、先轻重链剖分,按树剖更新时一样处理差分数组

对于每一条连续的部分执行$res[pos[top[x]]]++$和$res[pos[x]+1]--$

注意除去每个点的重复计算和对起点及终点的特殊处理!

 

2、直接对$res[dat[i]]++$和$res[dat[i+1]]++$,$res[lca]--$和$res[father[lca]]--$

最后从底至上进行更新即可

注意$LCA$处重复算了一次,要减一,而$father[LCA]$还要再减一才能满足差分

 

二者复杂度差不多,但(2)好像更妙一点

 

Solution1:

#include <bits/stdc++.h>

using namespace std;
const int MAXN=3e5+10;
int siz[MAXN],top[MAXN],dep[MAXN],pos[MAXN],f[MAXN][20],dat[MAXN],n,res[MAXN],cnt=0;
vector<int> G[MAXN]; 

void dfs1(int k,int anc) 
{
    siz[k]=1;
    for(int i=1;(1<<i)<=dep[k];i++)
        f[k][i]=f[f[k][i-1]][i-1];
    for(int i=0;i<G[k].size();i++)
    {
        int v=G[k][i];
        if(anc==v) continue;
        f[v][0]=k;dep[v]=dep[k]+1;
        dfs1(v,k);siz[k]+=siz[v];
    }
}

void dfs2(int k,int up) //树剖的处理
{
    int bs=0;pos[k]=++cnt;top[k]=up;
    for(int i=0;i<G[k].size();i++)
        if(G[k][i]!=f[k][0] && siz[G[k][i]]>siz[bs]) bs=G[k][i];
    if(!bs) return;
    
    dfs2(bs,up);
    for(int i=0;i<G[k].size();i++)
        if(G[k][i]!=f[k][0] && G[k][i]!=bs) dfs2(G[k][i],G[k][i]);
}

void LCA(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        res[pos[top[x]]]++;res[pos[x]+1]--;x=f[top[x]][0];
    }
    if(dep[x]<dep[y]) swap(x,y);
    res[pos[y]]++;res[pos[x]+1]--;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&dat[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        G[x].push_back(y);G[y].push_back(x);
    }
    
    dfs1(1,0);dfs2(1,1);
    
    for(int i=1;i<=n;i++)
    {
        if(i==1){res[pos[dat[i]]]++;res[pos[dat[i]]+1]--;continue;}        
        LCA(dat[i-1],dat[i]);res[pos[dat[i-1]]]--;res[pos[dat[i-1]]+1]++;  //减去对每个点重复算的一次
        if(i==n){res[pos[dat[i]]]--;res[pos[dat[i]]+1]++;}
    }
    for(int i=1;i<=cnt;i++) res[i]+=res[i-1];
    for(int i=1;i<=n;i++) printf("%d\n",res[pos[i]]);
    return 0;
}

Solution2:

#include <bits/stdc++.h>

using namespace std;
const int MAXN=3e5+10;
int siz[MAXN],dep[MAXN],f[MAXN][20],dat[MAXN],n,res[MAXN];
vector<int> G[MAXN]; 

void dfs(int k,int anc)
{
    for(int i=1;(1<<i)<=dep[k];i++)
        f[k][i]=f[f[k][i-1]][i-1];
    for(int i=0;i<G[k].size();i++)
    {
        int v=G[k][i];
        if(anc==v) continue;
        f[v][0]=k;dep[v]=dep[k]+1;dfs(v,k);
    }
}

int LCA(int x,int y)
{
    if(dep[x]<dep[y]) swap(x,y);
    int t=dep[x]-dep[y];
    for(int i=0;i<=18;i++)
        if(t&(1<<i)) x=f[x][i];
    for(int i=18;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    if(x==y) return x;
    return f[x][0];
}

void cnt(int k)  //从底至上更新
{
    for(int i=0;i<G[k].size();i++)
    {
        int v=G[k][i];
        if(v==f[k][0]) continue;
        cnt(v);res[k]+=res[v];
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&dat[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        G[x].push_back(y);G[y].push_back(x);
    }
    
    dfs(dat[1],0);
    for(int i=2;i<=n;i++)
    {
        int lca=LCA(dat[i-1],dat[i]);
        res[dat[i]]++;res[dat[i-1]]++;
        res[lca]--;res[f[lca][0]]--;
    }
    cnt(dat[1]);
    for(int i=2;i<=n;i++) res[dat[i]]--; //减去重复的次数
    for(int i=1;i<=n;i++) printf("%d\n",res[i]);
    return 0;
}

Review:

这题竟然是第一次写树上差分,

如果仅仅是两点间修改,而没有在线查询的话,直接差分的常数是要比区间加的效率高得多的

因此能用差分还是用差分

记住如果不是树剖的差分是从底至上更新

posted @ 2018-05-31 22:12  NewErA  阅读(159)  评论(0编辑  收藏  举报