SPOJ - COT2 : Count on a tree II (树上莫队)


题目链接:http://www.spoj.com/problems/COT2/en/


题目大意:

题意很简单,就是问你一棵树上任意两点间都多少不同的点。


解题思路:

这道题用到了传说中的树上莫队,其实还是很懵逼的,还不是很了解树上莫队的转换。树上莫队的详细解析可以看这篇博客http://blog.csdn.net/discreeter/article/details/52372689,自己讲是真的很难讲清楚,最后其实就是根据点的出现次数决定是否计算该点对答案的贡献。

自己代码也是照着大佬的来写的,主要根据代码来讲吧。


Ac代码:


#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int INF=1e9+7;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int n,m,a[N];   
int si,tag,pos[N];      //分块
int dep[N],par[N][21];      //dfs和求lca
int tmp,vis[N],tot[N],res[N];   //莫队算法
struct node     //储存询问
{
    int l,r;
    int id;
}qu[N];
stack<int> s;     //建立一个栈用于树分块
vector<int> v;    //离散化
vector<int> vk[N];//建图
int getid(int x) {return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
bool cmp(const node &p,const node &q)     //根据点所在块进行排序
{
    if(pos[p.l]==pos[q.l])
        return pos[p.r]<pos[q.r];
    return pos[p.l]<pos[q.l];
}
void init()
{
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i<=n;i++)
        par[i][j]=par[par[i][j-1]][j-1];
}
int dfs(int k)      //树分块
{
    int num=0;
    for(int i=0;i<(int)vk[k].size();i++)
    {
        int u=vk[k][i];
        if(!dep[u])
        {
            dep[u]=dep[k]+1,par[u][0]=k;
            num+=dfs(u);
            if(num>=si)
            {
                while(num--)
                {
                    pos[s.top()]=tag;
                    s.pop();
                }
                tag++;
            }
        }
    }
    s.push(k);
    return num+1;
}
int lca(int vv,int uu)      //求lca
{
    if(dep[vv]<dep[uu])
        swap(uu,vv);
    int d=dep[vv]-dep[uu];
    for(int i=0;(d>>i)!=0;i++)
        if((d>>i)&1) vv=par[vv][i];
    if(vv==uu)
        return vv;
    for(int i=20;i>=0;i--)
    {
        if(par[vv][i]!=par[uu][i])
        {
            vv=par[vv][i];
            uu=par[uu][i];
        }
    }
    return par[vv][0];
} 
void slove(int &v)      //莫队转移
{
    if(vis[v])      //如果该点之前出现过
    {
        if(--tot[a[v]]==0)  //出现次数-1 并判断-1后是否为0
            tmp--;          //为0则去掉对答案的贡献
    }
    else if(++tot[a[v]]==1) //判断出现次数+1后是否为1
        tmp++;              //增加对答案的贡献
    vis[v]^=1;              //取反
    v=par[v][0];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),v.push_back(a[i]);    //离散化 跟qsc大佬学的 感觉挺方便的
    sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=1;i<=n;i++)
        a[i]=getid(a[i]);
    for(int i=1;i<n;i++)    //建图
    {
        int uu,vv;
        scanf("%d%d",&uu,&vv);
        vk[uu].push_back(vv);
        vk[vv].push_back(uu);
    }
    si=(int)sqrt(n);    //分块及对树进行dfs
    dep[1]=1;
    dfs(1);
    while(!s.empty())   //多余的点重新分一个块
    {
        pos[s.top()]=tag;
        s.pop();
    }
    init();
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&qu[i].l,&qu[i].r);
        if(pos[qu[i].l]>pos[qu[i].r])   //保持pos小的在前
            swap(qu[i].l,qu[i].r);
        qu[i].id=i;
    }
    sort(qu+1,qu+1+m,cmp);  //对询问进行离线排序
    tmp=0;
    int cv=1,cu=1;
    for(int i=1;i<=m;i++)   //进行树上莫队的转移
    {
        int nv=qu[i].l,nu=qu[i].r;
        int la=lca(cv,nv);
        while(cv!=la) slove(cv);
        while(nv!=la) slove(nv);
        la=lca(cu,nu);
        while(cu!=la) slove(cu);
        while(nu!=la) slove(nu);
        cv=qu[i].l,cu=qu[i].r;
        la=lca(cv,cu);
        res[qu[i].id]=tmp+(!tot[a[la]]);
    }
    for(int i=1;i<=m;i++)
        printf("%d\n",res[i]);
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值