HDU 4358 Boring counting(离线 + 树状数组)

题目链接:Click here~~

题意:

给一颗 n 个节点的树,每个节点有权值 wi,然后 q 次询问,每次询问根为 u 的子树有多少个 wi 恰好出现了 k 次。

解题思路:

又是对 子树 的 操作/询问,而且满足区间性质,所以可以先将每棵子树转化成相应的区间。

于是问题变成,每次询问一段区间中有多少个数恰好出现了 k 次。

做法是将询问离线搞,离线只做过道,丝毫没功底,TAT。

先将所有询问按右端点排序,然后维护一个点值的集合,点值 j 表示的是区间 [j,i] 中恰好出现 k 次的数的个数。

考虑如何维护答案。为了方便起见,用 pos[v][m] 表示 v 第 m 次出现的位置。

对于新加入位置 i 的值 v,设这是 v 的第 p 次出现,这个事件只会使区间 [ pos[v][p-k]+1 , pos[v][p-k+1] ] 的结果加 1,使区间 [ pos[v][p-k-1]+1 , pos[v][p-k] ] 的结果减 1。

对于位置的维护可以先把每个值用 map 离散化成 [1,n] 以内,再用 vector[ ] 记录。点值的维护用树状数组可以搞定。

#pragma comment(linker,"/STACK:102400000,102400000")
#include <map>
#include <vector>
#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5;

vector<int> g[N],pos[N];

map<int,int> M;

int a[N],c[N],hash[N],ans[N],id;

pair<int,int> interval[N];

struct QAQ
{
    int l,r,id;
    QAQ(){}
    QAQ(int l,int r,int i):l(l),r(r),id(i){}
    bool operator < (const QAQ& S) const{
        return r < S.r;
    }
}query[N];

void dfs(int pre,int u)
{
    interval[u].first = ++id;
    hash[id] = u;
    for(int i=0;i<(int)g[u].size();i++)
    {
        int v = g[u][i];
        if(v == pre)
            continue;
        dfs(u,v);
    }
    interval[u].second = id;
}

inline int lowbit(int x){
    return x & -x;
}

void add(int loc,int val){
    while(loc < N){
        c[loc] += val;
        loc += lowbit(loc);
    }
}

void add(int a,int b,int val){
    add(a,val);
    add(b+1,-val);
}

int sum(int loc){
    int ret = 0;
    while(loc){
        ret += c[loc];
        loc -= lowbit(loc);
    }
    return ret;
}

int main()
{
    int T,n,k,Q, ncase = 0;
    scanf("%d",&T);
    while(T--)
    {
        M.clear();
        memset(c,0,sizeof(c));
        scanf("%d%d",&n,&k);
        id = 0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            if(!M.count(a[i]))
            {
                M[a[i]] = ++id;
                a[i] = id;
                pos[id].clear();
                pos[id].push_back(0);
            }
            else
                a[i] = M[ a[i] ];
            g[i].clear();
        }
        for(int i=0;i<n-1;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        id = 0;
        dfs(0,1);
        scanf("%d",&Q);
        for(int i=0;i<Q;i++)
        {
            int u;
            scanf("%d",&u);
            query[i] = QAQ(interval[u].first,interval[u].second,i);
        }
        sort(query,query+Q);
        int j = 0;
        for(int i=1;i<=n;i++)
        {
            int v = a[ hash[i] ];
            pos[v].push_back(i);
            int p = (int)pos[v].size() - 1;
            if(p >= k)
            {
                add(pos[v][p-k]+1,pos[v][p-k+1],1);
                if(p > k)
                    add(pos[v][p-k-1]+1,pos[v][p-k],-1);
            }
            while(j < Q && query[j].r == i)
            {
                ans[ query[j].id ] = sum(query[j].l);
                j++;
            }
        }
        if(ncase)
            puts("");
        printf("Case #%d:\n",++ncase);
        for(int i=0;i<Q;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值