关闭

SPOJ COT2 Count on a tree II 树上莫队

标签: spoj树上莫队
503人阅读 评论(0) 收藏 举报
分类:

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

题意:给定一棵树n个点,树上每个点都有一个权值。有m组查询,每个查询给出两个点,问这两点之间的路径上有多少种不同的权值

思路:树上莫队啊。有两种写法

第一种写法:通过一次dfs将树分块,然后查询按照左端点所在的块排序,依次查询。这之中重点是怎么进行区间转移,对两点的lca特殊处理,从别的博客上转载如下:

用S(v, u)代表 v到u的路径上的结点的集合。
用root来代表根结点,用lca(v, u)来代表v、u的最近公共祖先。
那么
S(v, u) = S(root, v) xor S(root, u) xor lca(v, u)
其中xor是集合的对称差。
简单来说就是节点出现两次消掉。
lca很讨厌,于是再定义
T(v, u) = S(root, v) xor S(root, u)
观察将curV移动到targetV前后T(curV, curU)变化:
T(curV, curU) = S(root, curV) xor S(root, curU)
T(targetV, curU) = S(root, targetV) xor S(root, curU)
取对称差:
T(curV, curU) xor T(targetV, curU)= (S(root, curV) xor S(root, curU)) xor (S(root, targetV) xor S(root, curU))
由于对称差的交换律、结合律:
T(curV, curU) xor T(targetV, curU)= S(root, curV) xorS(root, targetV)
两边同时xor T(curV, curU):
T(targetV, curU)= T(curV, curU) xor S(root, curV) xor S(root, targetV)
发现最后两项很爽……哇哈哈
T(targetV, curU)= T(curV, curU) xor T(curV, targetV)
(有公式恐惧症的不要走啊 T_T)
也就是说,更新的时候,xor T(curV, targetV)就行了。
即,对curV到targetV路径(除开lca(curV, targetV))上的结点,将它们的存在性取反即可。
——vfk博客

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define debug() puts("here")
using namespace std;

const int N = 500010;
int n, m, unit;
int arr[N], brr[N];
struct edge
{
    int to, next;
} g[N*2];
struct node
{
    int l, r, v, u, id;
    friend bool operator< (node a, node b)
    {
        return a.l != b.l ? a.l < b.l : a.r < b.r;
    }
}q[N*2];
int cnt, head[N];
int dep[N], par[N][21];
int pos[N], st[N], res[N*2];
int tmp, top, tag;
int tot[N], vis[N];
void add_edge(int v, int u)
{
    g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
int dfs(int v)
{
    int num = 0;
    for(int i = head[v]; i != -1; i = g[i].next)
    {
        int u = g[i].to;
        if(! dep[u])
        {
            dep[u] = dep[v] + 1, par[u][0] = v;
            num += dfs(u);
            if(num >= unit) //分块
            {
                while(num--) pos[st[--top]] = tag;
                tag++;
            }
        }
    }
    st[top++] = v; //储存待分块的序列
    return num + 1; //向上一层返回已被访问还未分块的点的个数
}
int LCA(int v, int u) //倍增法求LCA
{
    if(dep[v] < dep[u]) swap(v, u);
    int d = dep[v] - dep[u];
    for(int i = 0; (d>>i) != 0; i++)
        if((d>>i) & 1) v = par[v][i];
    if(v == u) return v;
    for(int i = 20; i >= 0; i--)
        if(par[v][i] != par[u][i]) v = par[v][i], u = par[u][i];
    return par[v][0];
}
void work(int &v)
{
    if(vis[v]) //已被记录,则本次去掉此点
    {
        if(--tot[arr[v]] == 0) tmp--;
    }
    else if(++tot[arr[v]] == 1) tmp++;
    vis[v] ^= 1;
    v = par[v][0];
}
void solve()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &arr[i]), brr[i] = arr[i];
    sort(brr + 1, brr + 1 + n);
    for(int i = 1; i <= n; i++) arr[i] = lower_bound(brr + 1, brr + 1 + n, arr[i]) - brr;
    int a, b;
    cnt = 0;
    memset(head, -1, sizeof head);
    for(int i = 1; i < n; i++)
        scanf("%d%d", &a, &b), add_edge(a, b), add_edge(b, a);
    unit = (int)sqrt(n);
    dep[1] = 1;
    dfs(1);
    while(top) pos[st[--top]] = tag; //最后一部分没有分块的点,分块
    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];
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &q[i].v, &q[i].u);
        if(pos[q[i].v] > pos[q[i].u]) swap(q[i].v, q[i].u);
        q[i].id = i, q[i].l = pos[q[i].v], q[i].r = pos[q[i].u]; //确定两个点分别位于的块
    }
    sort(q + 1, q + 1 + m); //分块排序
    tmp = 0;
    int cv = 1, cu = 1;
    for(int i = 1; i <= m; i++)
    {
        int nv = q[i].v, nu = q[i].u;
        int lca = LCA(cv, nv);//两点朝lca移动,处理路径上的点
        while(cv != lca) work(cv);
        while(nv != lca) work(nv);
        lca = LCA(cu, nu);
        while(cu != lca) work(cu);
        while(nu != lca) work(nu);
        cv = q[i].v, cu = q[i].u;
        lca = LCA(cv, cu);
        res[q[i].id] = tmp + (!tot[arr[lca]]);//对lca特殊处理
    }
    for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
}
int main()
{
    solve();
    return 0;
}

第二种:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long ll;
const int N = 50010;
struct edge
{
    int to, next;
}g[N*2];
struct node
{
    int l, r, anc, id;
}p[N*2];
int n, m, unit;
int arr[N], brr[N];
int cnt, head[N];
int par[N][20], dep[N];
int in[N], out[N], lst[N*2], num;
int res[N*2], tmp, ver[N];
bool vis[N];
void add_edge(int v, int u)
{
    g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
void dfs(int v)
{
    in[v] = ++num;
    lst[num] = v;
    for(int i = head[v]; i != -1; i = g[i].next)
    {
        int u = g[i].to;
        if(! dep[u])
            par[u][0] = v, dep[u] = dep[v] + 1, dfs(u);
    }
    out[v] = ++num;
    lst[num] = v;
}
int LCA(int v, int u)
{
    if(dep[v] < dep[u]) swap(v, u);
    int d = dep[v] - dep[u];
    for(int i = 0; (d>>i) != 0; i++)
        if((d>>i) & 1) v = par[v][i];
    if(v == u) return v;
    for(int i = 19; i >= 0; i--)
        if(par[v][i] != par[u][i]) v = par[v][i], u = par[u][i];
    return par[v][0];
}
void update(int i)
{
    //树上每个点在每次查询时只能被访问一次,若之前没访问过,那就是说明当前点应当在路径中,否则,不在路径中
    if(vis[lst[i]]) //当前点被访问过,那么一定被计数了
    {
        ver[arr[lst[i]]]--;
        if(! ver[arr[lst[i]]]) tmp--; //当这个权值不存在时,才把计数减1
        vis[lst[i]] = false;
    }
    else
    {
        if(! ver[arr[lst[i]]]) tmp++; //当这个权值本来不存在时,把计数加1
        ver[arr[lst[i]]]++;
        vis[lst[i]] = true;
    }
}
void solve()
{
    int a, b;
    for(int i = 1; i <= n; i++)
        scanf("%d", &arr[i]), brr[i] = arr[i];
    sort(brr + 1, brr + 1 + n);
    for(int i = 1; i <= n; i++) arr[i] = lower_bound(brr + 1, brr + 1 + n, arr[i]) - brr; //离散化
    cnt = 0;
    memset(head, -1, sizeof head);
    for(int i = 1; i <= n - 1; i++) //存树
        scanf("%d%d", &a, &b), add_edge(a, b), add_edge(b, a);
    memset(dep, 0, sizeof dep);
    dep[1] = 1;
    num = 0;
    par[1][0] = 1;
    dfs(1);
    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];
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d", &a, &b);
        p[i].id = i;
        if(a == b) p[i].anc = -1; //特殊标记,此时公共祖先应该为1
        else
        {
            p[i].anc = LCA(a, b);
            if(p[i].anc != a && p[i].anc != b) //画个图很快就可以看明白
                p[i].l = min(out[a], out[b]), p[i].r = max(in[a], in[b]);
            else
                p[i].l = min(in[a], in[b]), p[i].r = max(in[a], in[b]);
        }
    }
    unit = (int)sqrt(num);
    memset(vis, 0, sizeof vis);
    sort(p + 1, p + 1 + m, [](node a, node b){return a.l/unit != b.l/unit ? a.l/unit < b.l/unit : a.r < b.r;});//分块排序
    int l = 1, r = 0;
    tmp = 0;
    for(int i = 1; i <= m; i++)
        if(p[i].anc == -1) res[p[i].id] = 1; //查询的两点为同一点,则种类只有1
        else
        {
            for(; r < p[i].r; r++) update(r + 1);
            for(; r > p[i].r; r--) update(r);
            for(; l < p[i].l; l++) update(l);
            for(; l > p[i].l; l--) update(l - 1);
            if(lst[p[i].l] == p[i].anc || lst[p[i].r] == p[i].anc) res[p[i].id] = tmp; //其中一点为两点的公共祖先
            else
            { //此时所访问的路径中不包括两点的lca,所以要对lca特别处理
                update(in[p[i].anc]);
                res[p[i].id] = tmp;
                update(in[p[i].anc]);
            }
        }
    for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
}
int main()
{
    scanf("%d%d", &n, &m);
    solve();
    return 0;
}


1
0
查看评论

[spoj10707]Count on a tree II 解题报告

一开始不知道这是主席出的神题,不小心点开了。。结果做了1天(想了半天+写了半天)。 我是学莫队的时候在某大神的莫队课件里看到这道题的,他说可以用莫队做。(但这真的算莫队么?) 这道题非常奇怪,就是把 HH的项链 搬到了树上,但是这样就变得好难。。我一开始的想法跟po姐姐的做法差不多,就是把dfs...
  • TA201314
  • TA201314
  • 2016-04-06 16:13
  • 1211

SPOJ COT2 Count on a tree II [树上莫队]

合成公式 : 离散化+树上分块+LCA+排序 == 树上莫队
  • GrassTreeFlower
  • GrassTreeFlower
  • 2015-09-10 11:52
  • 425

SPOJ COT2 Count on a tree II 树上莫队

题目:http://www.spoj.com/problems/COT2/en/ 题意:给定一棵树n个点,树上每个点都有一个权值。有m组查询,每个查询给出两个点,问这两点之间的路径上有多少种不同的权值 思路:树上莫队啊。有两种写法 #include #include #include #inc...
  • discreeter
  • discreeter
  • 2016-08-30 20:28
  • 503

SPOJ COT2 树上的莫队算法,树上区间查询

题意:n个节点形成的一棵树。每个节点有一个值。m次查询,求出(u,v)路径上出现了多少个不同的数。 树上的莫队算法,同样将树分成siz=sqrt(n)块,然后离线操作。先对树dfs一遍,每当子树节点个数num>=siz,就将这num个分成一块。读取所有的查询按左端点所在块排序。 重点在于怎么进...
  • HTT_H
  • HTT_H
  • 2015-08-17 19:09
  • 1424

SPOJ COT2

【标签】离散化,数据结构,分治,图论 【题意】 You are given a tree with N nodes. The tree nodes are numbered from 1 to N. Each node has an integer weight. We will ask you...
  • u013598409
  • u013598409
  • 2015-04-08 17:26
  • 967

BZOJ 2588: Spoj 10628. Count on a tree|主席树

用主席树维护这个点到根上的数,然后Lca上的数单独处理!!!!!!!!!#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<queue>...
  • ws_yzy
  • ws_yzy
  • 2016-02-19 18:46
  • 349

【可持久化线段树】[SPOJ COT]Count on a tree

题目大意:给定一棵树,然后询问连个节点间路径上的权值的第K小的权值大小 题目分析:和普通的第K大的可持久化线段树差距不大,但是要写个LCA可以发现Tree(a)+Tree(b)−Tree(LCA)−LCA(fa[LCA])Tree(a)+Tree(b)-Tree(LCA)-LCA(fa[LCA])...
  • JeremyGJY
  • JeremyGJY
  • 2015-11-27 13:47
  • 333

SPOJ - COT Count on a tree [LCA+主席树]【数据结构】

题目链接:http://www.spoj.com/problems/COT/en/ —————————————————————————————————————— COT - Count on a tree #tree You are given a tree with N nodes.Th...
  • qq_33184171
  • qq_33184171
  • 2017-03-12 20:26
  • 445

SPOJ COT Count on a tree(树上路径第k小 主席树)

题意: 求树上A,B两点路径上第K小的数 分析: 同样是可持久化线段树,只是这一次我们用它来维护树上的信息。 我们之前已经知道,可持久化线段树实际上是维护的一个前缀和,而前缀和不一定要出现在一个线性表上。 比如说我们从一棵树的根节点进行DFS,得到根节点到各节点的距离dist[x]——这是...
  • u014492306
  • u014492306
  • 2015-08-26 15:57
  • 420

spoj Count on a tree【主席树+在线LCA】

10628. Count on a tree Problem code: COT   You are given a tree with N nodes.The tree nodes are numbered from 1 to...
  • zhou_yujia
  • zhou_yujia
  • 2016-01-25 23:36
  • 467
    个人资料
    • 访问:170933次
    • 积分:7498
    • 等级:
    • 排名:第3473名
    • 原创:603篇
    • 转载:5篇
    • 译文:0篇
    • 评论:28条
    ~~~ACMER~~~
    最新评论