HDU 4358 Boring counting(树的遍历+树状数组+离散化+离线处理)

273 篇文章 0 订阅
112 篇文章 0 订阅

HDU 4358 Boring counting(树的遍历+树状数组+离散化+离线处理)

分析:

       首先读入所有的节点权重存在nodes[n]中,然后读入n-1条边,1为根节点.把整个图做成一个邻接表的形式.用vector<int> g[n],其中g[i]表示与i邻接的点都有哪些.然后对1号节点执行中序遍历.

首先本题是对整棵子树的查询,其实我们可以对该树执行一次后序遍历,把我们访问到的节点编号一一保存到nodes[i].index=j中 (i是这个节点原来的编号,j是表示我们中序遍历这棵树时,访问该点时是第j个访问的).

现在每个根节点的查询其实就正好对应了一个连续的区间查询.(想想是不是)并把每个根节点所对应的区间L[i]=j1和R[i]=j2记下来. (j1和j2是节点新的编号,是在执行初始i节点的后序遍历后,所有节点重新编号后,查询的区间是[j1,j2])

       后序遍历后,给所有节点的权重重新编号后存在了a[i]中,我们如果要查询初始x节点的子树,只需要查询a[i]数组内在区间[L[x],R[x]]中符合要求的值有多少个.

       接下来我们首先把a[i]的值重新映射集中(因为a[i]最大可能10亿,但却只有10W个a[i]值)到数组b[i]中.

       离线处理每个查询,把每个查询按L从小到大排序.

       预处理:接下来我们建一棵树状数组A[n],然后从1到n扫描b[i]数组,如果对于b[i]=x的值是正好第k次出现了,我们就add(i,1),且我们需要找到b[i]值正好出现了第k+1次的位置y,执行add(y,-1).扫描完b[n]之后,我们保证sum(R)的值就是对所有区间[1,R]的查询结果.

       接下来我们需要查询所有[2,R]区间的结果,我们首先要消除b[1]对后续数列产生的影响,我们找到b[1]值第k次出现的位置y1和第k+1次出现的位置y2和第k+2次出现的位置y3(其实就是上一次针对b[1]的值执行add(,1)和add(,-1)的位置),所以我们执行add(y1,-1)和add(y2,2)和add(y3,-1). 消除影响后,b[1]就好像从来没有出现过一样.

       完成上面那步,我们保证sum(R)的值就是对所有区间[2,R]的查询结果.

当我们查询完sum(R)表示区间[i,R]的查询结果后,我们需要找到当前d[i]值从i位置开始第k次出现的位置y1(包括i,且d[i]就算是第一次出现的位置),和d[i]值第k+1次出现的位置y2,和d[i]值第k+2次出现的位置y3,执行add(y1,-1) 和 add(y2,2)  和add(y3,-1)

原始代码:超时

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN =100000+100;
const int MAXM =100000+100;

struct node
{
    int v;
    int index;
    bool operator <(const node&b)const
    {
        return v<b.v;
    }
}nodes[MAXN],a[MAXN];//初始树的权值
struct command
{
    int l,r;
    int index;
    bool operator <(const command&b)const
    {
        return l<b.l;
    }
}coms[MAXM];//查询
int L[MAXN],R[MAXN],cnt;
int b[MAXN];
int ans[MAXM];
int cur[MAXN];//cur[i]表示b[j]=i值当前处理阶段第k次出现的位置
int first[2*MAXN],next[2*MAXN],u[2*MAXN],v[2*MAXN];
void add(int i,int j,int num)
{
    u[num]=i;
    v[num]=j;
    next[num]=first[u[num]];
    first[i]=num;
}
int dfs(int fa,int a)//后序访问以u为根节点(fa是u的父节点)的树,并返回该树的最最左下角的点的新编号
{
    int min_num=cnt;
    for(int e=first[a];e!=-1;e=next[e])if(v[e]!=fa)
    {
        dfs(a,v[e]);
    }
    L[a]=min_num;
    R[a]=cnt;
    nodes[a].index=cnt++;
    return L[a];
}
struct HASHMAP
{
    int head[MAXN];
    int next[MAXN];//把所有相同的b[i]值从前到后串联起来
    void init()
    {
        memset(head,-1,sizeof(head));
    }
    int insert(int i,int v)
    {
        int num=1;//表示插入的这个b[i]是值为v的第num次出现了
        next[i]=-1;
        if(head[v]==-1)
        {
            head[v]=i;
        }
        else
        {
            int j=head[v];
            num++;
            while(next[j]!=-1)
            {
                j=next[j];
                num++;
            }
            next[j]=i;
        }
        return num;
    }
    int find(int i)//找到b[i]值相同的后继元素的下标
    {
        if(i==-1)
            return -1;
        return next[i];
    }
}hm;
int c[MAXN];
int lowbit(int x)
{
    return x&(-x);
}
int sum(int x)
{
    int res=0;
    while(x)
    {
        res +=c[x];
        x-=lowbit(x);
    }
    return res;
}
void add(int x,int v)
{
    while(x<MAXN)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}
int main()
{
    int T,kase=1;
    scanf("%d",&T);
    while(T--)
    {
        printf("Case #%d:\n",kase++);
        int n,K;
        scanf("%d%d",&n,&K);
        for(int i=1;i<=n;i++)//读权重
        {
            scanf("%d",&nodes[i].v);
        }
        memset(first,-1,sizeof(first));
        for(int i=1;i<n;i++)//读边
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v,i*2-1);
            add(v,u,i*2);
        }
        cnt=1;//记录当前递归到了第几个点
        dfs(-1,1);
        for(int i=1;i<=n;i++)
        {
            a[nodes[i].index].v = nodes[i].v;
            a[nodes[i].index].index = nodes[i].index;
        }
/*
        for(int i=1;i<=n;i++)
        {
            printf("%d %d %d\n",a[nodes[i].index],L[i],R[i]);
        }
*/
        sort(a+1,a+n+1);
        int max_num=1;
        b[a[1].index]=1;
        for(int i=2;i<=n;i++)
        {
            if(a[i].v==a[i-1].v)
                b[a[i].index]=b[a[i-1].index];
            else
                b[a[i].index]=++max_num;
        }
        hm.init();
        memset(cur,-1,sizeof(cur));
        for(int i=1;i<=n;i++)
        {
            int num = hm.insert(i,b[i]);
            if(num==K)
                cur[b[i]]=i;//b[i]值第K次出现的位置是i
        }
/*
        for(int i=1;i<=n;i++)
            printf("%d ",b[i]);
        printf("\n");
        for(int i=1;i<=n;i++)
        {
            printf("%d %d\n",L[i],R[i]);
        }
        printf("***************\n");
*/
        int Q;
        scanf("%d",&Q);
        for(int i=1;i<=Q;i++)
        {
            int root;
            scanf("%d",&root);
            coms[i].l=L[root];
            coms[i].r=R[root];
            coms[i].index=i;
        }
        sort(coms+1,coms+Q+1);
        for(int i=1;i<=n;i++)//预处理
        {
            int y1=cur[i];
            int y2=hm.find(y1);
            if(y1>-1)
            {
                add(y1,1);
                if(y2>-1)
                    add(y2,-1);
            }
        }
        int j=1;//表示当前处理排序后的第j条命令
        for(int i=1;i<=n;i++)
        {
            while(coms[j].l==i)
            {
                ans[coms[j].index] = sum(coms[j].r);
                j++;//别忘了
            }
            if(j>Q)
                break;
            int y1=cur[b[i]];
            int y2=hm.find(y1);
            int y3=hm.find(y2);
            if(y1>0)
            {
                add(y1,-1);
                if(y2>0)
                {
                    add(y2,2);
                    if(y3>0)
                    {
                        add(y3,-1);
                    }
                }
            }
            cur[b[i]]=y2;//这一句别忘了,更新d[i]下一个第k次出现的位置
        }
        for(int i=1;i<=Q;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

现在换另一种做法,前面几步都一样,知道预处理那里,现在不预处理了.而是将所有询问按照R从小到大排序(如果R相同,则按L从小到大排序).

我们从1到n读入每一个b[i]的值,并且用一个vector<int> vec[MAXN]记录vec[b[i]][k]=j表示b[i]值已经出现了k次且第k次出现在j位置.

如果当前size = vec[b[i]].size()-1正好等于k(因为vec[x][0]=0,顶掉了这个0位,所以多存了一个0),那么执行add( vec[b[i]][size-k]+1,1 ); 和add( vec[b[i]][size-k+1]+1,-1 );

此时我们保证sum(L)就是所有区间[L,i]的查询值(其中L是可变的,但是i是固定的也就是说只有当前查询存在R==i才查询,如果没有R==i,就继续读入下一个。)

如果当前size = vec[b[i]].size()-1正好大于k(因为vec[x][0]=0,顶掉了这个0位,所以多存了一个0),那么执行add( vec[b[i]][size-k-1]+1,-1 ); 和add( vec[b[i]][size-k]+1,2 ); 和 add( vec[b[i]][size-k+1]+1,-1 );

(自己在数轴上画图验证一下看看是不是)

1.下次如果需要离散化的数据,尽量先进行离散化在做其他操作。

2.由于没有初始化c数组,导致WA1小时,真是郁闷。以后全局变量和数组都在最前面统一初始化。

3.如果栈可能溢出,记得栈中的变量尽量少就不会溢出,否则就用自己手写的栈。

AC代码:765ms。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

//HDU开栈外挂,本代码不用也可以AC
#pragma comment(linker, "/STACK:102400000,102400000")

const int MAXN =100000+5000;
const int MAXM =100000+5000;
vector<int> vec[MAXN];
struct node
{
    int v;
    int index;
    bool operator <(const node&b)const
    {
        return v<b.v;
    }
} nodes[MAXN],a[MAXN]; //初始树的权值
struct command
{
    int l,r;
    int index;
    bool operator <(const command&b)const
    {
        return r<b.r ;
    }
} coms[MAXM]; //查询
int L[MAXN],R[MAXN],cnt;
int b[MAXN];
int ans[MAXM];
int cur[MAXN];//cur[i]表示b[j]=i值当前处理阶段第k次出现的位置
int first[2*MAXN],next[2*MAXN],u[2*MAXN],v[2*MAXN];
void add(int i,int j,int num)
{
    u[num]=i;
    v[num]=j;
    next[num]=first[u[num]];
    first[i]=num;
}
void dfs(int fa,int a)//后序访问以u为根节点(fa是u的父节点)的树,并返回该树的最最左下角的点的新编号
{
    L[a]=cnt;
    for(int e=first[a]; e!=-1; e=next[e])if(v[e]!=fa)
        {
            dfs(a,v[e]);
        }
    R[a]=cnt;
    nodes[a].index=cnt++;
}

int c[MAXN];
int lowbit(int x)
{
    return x&(-x);
}
int sum(int x)
{
    int res=0;
    while(x)
    {
        res +=c[x];
        x-=lowbit(x);
    }
    return res;
}
void add(int x,int v)
{
    while(x<MAXN)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    for(int kase=1; kase<=T; kase++)
    {
        memset(first,-1,sizeof(first));
        memset(c,0,sizeof(c));
        cnt=1;//记录当前递归到了第几个点

        int n,K;
        scanf("%d%d",&n,&K);
        for(int i=1; i<=n; i++) //读权重
        {
            scanf("%d",&nodes[i].v);
        }

        for(int i=1; i<n; i++) //读边
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v,i*2-1);
            add(v,u,i*2);
        }

        dfs(-1,1);
        for(int i=1; i<=n; i++)
        {
            a[nodes[i].index].v = nodes[i].v;
            a[nodes[i].index].index = nodes[i].index;
        }

        sort(a+1,a+n+1);
        int max_num=1;
        b[a[1].index]=1;
        for(int i=2; i<=n; i++)
        {
            if(a[i].v==a[i-1].v)
                b[a[i].index]=b[a[i-1].index];
            else
                b[a[i].index]=++max_num;
        }
        int Q;
        scanf("%d",&Q);
        for(int i=1; i<=Q; i++)
        {
            int root;
            scanf("%d",&root);
            coms[i].l=L[root];
            coms[i].r=R[root];
            coms[i].index=i;
        }
        sort(coms+1,coms+Q+1);

        for(int i=1; i<=n; i++)
        {
            vec[i].clear();
            vec[i].push_back(0);
        }
        int j=1;//表示当前处理排序后的第j条命令
        for(int i=1; i<=n; i++)
        {
            vec[b[i]].push_back(i);
            int size = vec[b[i]].size()-1;
            if(size>=K)
            {
                if(size==K)
                {
                    add( vec[b[i]][size-K]+1,1 );
                    add( vec[b[i]][size-K+1]+1,-1 );
                }
                else if(size>K)
                {
                    add( vec[b[i]][size-K-1]+1,-1 );
                    add( vec[b[i]][size-K]+1,2 );
                    add( vec[b[i]][size-K+1]+1,-1 );
                }
            }
            while(coms[j].r==i && j<=Q)
            {
                ans[coms[j].index] = sum( coms[j].l );
                j++;
            }
            if(j>Q)
                break;
        }
        printf("Case #%d:\n",kase);
        for(int i=1; i<=Q; i++)
            printf("%d\n",ans[i]);
        if(kase<T)
            printf("\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值