codeforces 221 div1 D Tree and Queries

题目大意:给一棵树,1为根,每个节点有一个权值。多次询问,每次询问为一个v和k。把v为根的这棵树中,所有节点的权加入一个集合。求,这个集合中,有几种数字出现的次数大于等于k。


最开始,老是去想可持续化数据结构,但是,我不会可持续化的平衡树啊 -_-|||

然后去找解题,有种看起来很叼的暴力算法,但是我看不懂啊。。。

最后,一位大神告诉我这是莫队算法。这里十分感谢这位大神。

我们还是使用欧拉序列把树形结构变成线性结构,问题变成了区间查询问题。


莫队算法算的上是对区间询问问题的一种离线的方法。

我们以无修改值的区间求和为例。

首先说一种最暴力的办法,即将区间每个点都加起来。当然,这是要超时的。

好吧,我们改进一下,假设有两次查询(第一次记为[l1,r1] ,第二次记为[l2,r2])。第一次还是暴力,第二次建立在第一次的结果上,当移动左右边界即可,复杂度为O(|l1-l2|+lr1-r2l)。如果有多次,还是按移动左右边界的方法。

这方法也不靠谱啊,感觉要超时。读者先不要着急。

我们计算前,先对多次查询排个序。假设数组有n个元素,记 int S=sqrt(n),询问的区间为[l,r],排序的的第一关键值为 l/S,第二关键值为 r。

排好序后,再按上面那种做法写,综合复杂度即为n*sqrt(n)。

下面说说为什么是n*sqrt(n)。

按第一关键值排序,等于把查询分成了sqrt(n)块。

对于每一块,每次左边界的移动次数是不会超过sqrt(n)的。在每一块中,按r从小到大排序(即r不降),那么,对于这一块,右边界的均摊复杂度最坏为O(n)。

而从一块移动到另一块,左右边界的移动次数也是不超过n次。

总共有sqrt(n)块,综合复杂度不会超过n*sqrt(n)。


和大神比起来,代码好丑啊。。。

//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<cctype>
#include<string>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<map>
#include<set>
using namespace std;
#define MP(x,y) make_pair((x),(y))
#define PB(x) push_back(x)
typedef __int64 LL;
//typedef unsigned __int64 ULL;
/* ****************** */
const int INF=1000111222;
const double INFF=1e200;
const double eps=1e-8;
const int mod=1000000007;
const int NN=100010;
const int MM=10010;
/* ****************** */

int a[NN],b[NN];
struct G
{
    int v,next;
}E[NN*2];
int p[NN],T,tsp;
int ll[NN],rr[NN];
struct Q
{
    int l,r,id,fg,k;
}line[NN];
int ans[NN],num[NN],ff[NN];

void add(int u,int v)
{
    E[T].v=v;
    E[T].next=p[u];
    p[u]=T++;
}

void dfs(int u,int fa)
{
    ll[u]=++tsp;
    a[tsp]=b[u];
    int i,v;
    for(i=p[u];i+1;i=E[i].next)
    {
        v=E[i].v;
        if(fa==v)
            continue;
        dfs(v,u);
    }
    rr[u]=tsp;
}

bool cmp(Q x,Q y)
{
    if(x.fg==y.fg)
        return x.r<y.r;
    return x.fg<y.fg;
}

void solve(int n,int m)
{
    sort(line+1,line+1+m,cmp);
    int l,r,i;
    l=1;
    r=0;
    for(i=1;i<=m;i++)
    {
        while(l>line[i].l)
        {
            l--;
            num[a[l]]++;
            ff[ num[a[l]] ]++;
        }
        while(l<line[i].l)
        {
            ff[ num[a[l]] ]--;
            num[a[l]]--;
            l++;
        }
        while(r>line[i].r)
        {
            ff[ num[a[r]] ]--;
            num[a[r]]--;
            r--;
        }
        while(r<line[i].r)
        {
            r++;
            num[a[r]]++;
            ff[ num[a[r]] ]++;
        }
        ans[ line[i].id ]=ff[ line[i].k ];
    }
    for(i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
}

int main()
{
    memset(p,-1,sizeof(p));
    T=tsp=0;

    int n,m,i,u,v,S;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
        scanf("%d",&b[i]);
    for(i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }

    dfs(1,-1);
    S=sqrt(n+0.0);

    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&v,&line[i].k);
        line[i].id=i;
        line[i].l=ll[v];
        line[i].r=rr[v];
        line[i].fg=ll[v]/S;
    }

    solve(n,m);

    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值