题意:

给出一片森林,问能找到多少个三元组(x,y,z)满足f[y]>f[x]和f[z]>f[x],并且x是y的祖先,y是z的祖先。

思路:

对于每个点作为y,答案为y的祖先中f比它小的乘以y的子树中f比它小的。y的子树中f比它小的,之前上一篇博文中用dfs序解决了这个问题,但是这里考虑到这里比较的不是编号而是f,所以像那个做法比较麻烦,所以可以考虑另一种做法,对于一个点,进入它的时候查询当前比它小的结点数(用树状数组维护权值),从它出来的时候查询当前比它小的结点数,两者相减就是子树中比它小的结点数。

y的祖先中f比它小的,可以在进入的时候查询当前比它小的结点,出来的时候也要把它从树状数组里删除,这样可以保证每次查询的时候只有祖先。

所以这样每棵树只要搜一次,最后把答案相加即可。

这道题目卡内存,所以为了省内存,因为这是一棵树,而且肯定是从根结点搜下来的,所以可以只保存从父节点到子结点的那条边,vis数组也可以省略。

代码:

typedef long long ll;
vector<int>v[1000010];
int fa[1000010];
int w[1000010];
int d1[1000010],d2[1000010];
ll tot;
int lowbit(int x)
{
    return x&(-x);
}
int query(int a[],int x)
{
    int res=0;
    while(x)
    {
        res+=a[x];
        x-=lowbit(x);
    }
    return res;
}
void add(int a[],int x,int v)
{
    while(x<=1000000)
    {
        a[x]+=v;
        x+=lowbit(x);
    }
}
void dfs(int id,int par)
{
    int in=query(d1,w[id]-1);
    add(d1,w[id],1);
    add(d2,w[id],1);
    for(int i=0;i<v[id].size();i++)
    {
        if(v[id][i]!=par)
            dfs(v[id][i],id);
    }
    add(d2,w[id],-1);
    tot+=(ll)(query(d1,w[id]-1)-in)*query(d2,w[id]-1);
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            v[i].clear();
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&fa[i]);
            if(fa[i]!=0)
            {
                //v[i].push_back(fa[i]);
                v[fa[i]].push_back(i);
            }
        }
        for(int i=1;i<=n;i++)
            scanf("%d",&w[i]);
        tot=0;
        memset(d1,0,sizeof(d1));
        memset(d2,0,sizeof(d2));
        for(int i=1;i<=n;i++)
            if(fa[i]==0)
                dfs(i,i);
        printf("%lld\n",tot);
    }
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

徐州邀请赛G 分块思想+前缀积后缀积 Previous
HDU3887 dfs序+树状数组 Next