莫队算法

普通莫队算法:
//给定一个以1为根的树,树上每个节点都有权值,然后问以某点v为根的子树中有多少个出现次数等于k的权值
//先用dfs标号把树转换为线性,然后套用线性的莫队算法
const int N = 100010;
struct edge
{
    int to, next;
} g[N*2];
struct node
{
    int l, r, id;
} q[N];
int cnt, head[N];
int n, m, k, block, tmp, cas;
int a[N], b[N], c[N], arr[N], in[N], out[N], num[N], val[N], ans[N], tot;
void add_edge(int v, int u)
{
    g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
void dfs(int v, int fa)
{
    in[v] = ++tot;
    val[tot] = a[v];//把节点的权值映射到线性区间上
    for(int i = head[v]; i != -1; i = g[i].next)
    {
        int u = g[i].to;
        if(u != fa) dfs(u, v);
    }
    out[v] = tot;
}
bool cmp(node a, node b)
{
    return a.l/block != b.l/block ? a.l/block < b.l/block : a.r < b.r;
}
void add(int x)
{
    num[x]++;
    if(num[x] == k) tmp++;
    else if(num[x] == k + 1) tmp--;
}
void del(int x)
{
    num[x]--;
    if(num[x] == k) tmp++;
    else if(num[x] == k - 1) tmp--;
}
void work()
{
    tot = 0;
    dfs(1, -1);
    for(int i = 1; i <= m; i++)
        q[i].id = i, q[i].l = in[arr[i]], q[i].r = out[arr[i]];
    block = (int)sqrt(1.0*n);
    sort(q+1, q+1+m, cmp);
    memset(num, 0, sizeof num);
    int l = 1, r = 0;
    tmp = 0;
    for(int i = 1; i <= m; i++)
    {
        while(r < q[i].r) add(val[++r]);
        while(r > q[i].r) del(val[r--]);
        while(l < q[i].l) del(val[l++]);
        while(l > q[i].l) add(val[--l]);
        ans[q[i].id] = tmp;
    }
    printf("Case #%d:\n", ++cas);
    for(int i = 1; i <= m; i++) printf("%d\n", ans[i]);
}
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
        sort(b+1, b+1+n);
        for(int i = 1; i <= n; i++) a[i] = lower_bound(b+1, b+1+n, a[i]) - b;
        cnt = 0;
        memset(head, -1, sizeof head);
        int v, u;
        for(int i = 1; i <= n - 1; i++)
        {
            scanf("%d%d", &v, &u);
            add_edge(v, u), add_edge(u, v);
        }
        scanf("%d", &m);
        for(int i = 1; i <= m; i++) scanf("%d", &arr[i]);
        work();
        if(t) printf("\n");
    }
    return 0;
}
带修改莫队算法:

带修改莫队和普通莫队其实差不多,普通莫队按两个关键字对询问排序,首先按左端点所在的块从小到大排序,否则按右端点排序。带修改莫队按三个关键字排序,第三个关键字是本次询问时前一个修改的位置,首先按左端点所在的块从小到大排序,否则按右端点所在的块从小到大排序,否则按第三个关键字直接排序。然后查询时,暴力还原修改到本次查询时的状态,其他的就都一样了。代码中的vis[x]为1时代表x位置的值被统计在答案中,否则不是

//给出一个有n个元素的数组,有以下两种操作:Q x y,求出区间[x,y)内不同元素的个数,M x y,把第x个元素的值修改为y。题目中的下标是从0开始的
const int N = 50000 + 10, M = 1000000 + 10;

struct node
{
    int l, r, id, pre;
}q[N];
struct
{
    int x, v, o;
}que[N];

int n, m;
int block, tot, top, val;
int a[N], last[N], pos[N], ans[N], num[M];
bool vis[N];
bool cmp(node a, node b)
{
//    return (pos[a.l] < pos[b.l]) || (pos[a.l]==pos[b.l] && pos[a.r] < pos[b.r])
//        || (pos[a.l] == pos[b.l] && pos[a.r] == pos[b.r] && a.pre < b.pre);
    if(pos[a.l] == pos[b.l] && pos[a.r] == pos[b.r]) return a.pre < b.pre;
    else if(pos[a.l] == pos[b.l]) return pos[a.r] < pos[b.r];
    else return pos[a.l] < pos[b.l];
}
void init()
{
    //block = sqrt(n);
    block = 1300;//设为1300sqrt(n)块,蜜汁。。。
    for(int i = 1; i <= n; i++) pos[i] = (i-1) / block + 1;
    tot = top = 0;
}
void update(int x)
{
    if(vis[x])
    {
        if(--num[a[x]] == 0) val--;
    }
    else
    {
        if(++num[a[x]] == 1) val++;
    }
    vis[x] ^= 1;
}
void restore(int x, int v)
{
    if(vis[x])
    {
        update(x); a[x] = v; update(x);
    }
    else a[x] = v;
}
void work()
{
    sort(q + 1, q + 1 + tot, cmp);
    val = 0;
    int l = 1, r = 0, now = 0;
    for(int i = 1; i <= tot; i++)
    {
        while(now < q[i].pre) ++now, restore(que[now].x, que[now].v);
        while(now > q[i].pre) restore(que[now].x, que[now].o), --now;
        while(r < q[i].r) update(++r);
        while(r > q[i].r) update(r--);
        while(l < q[i].l) update(l++);
        while(l > q[i].l) update(--l);
        ans[q[i].id] = val;
    }
    for(int i = 1; i <= tot; i++) printf("%d\n", ans[i]);
}
int main()
{
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]), last[i] = a[i];
    char ch;
    int x, y;
    for(int i = 1; i <= m; i++)
    {
        scanf(" %c%d%d", &ch, &x, &y);
        x++;
        if(ch == 'Q') q[++tot].l = x, q[tot].r = y, q[tot].id = tot, q[tot].pre = top;
        else que[++top].x = x, que[top].v = y, que[top].o = last[x], last[x] = y;
        //que[x].x记录修改位置,que[top].v记录修改后的值,que[top].o记录修改前的值
    }
    work();
    return 0;
}

树上莫队

//给定一棵树n个点,树上每个点都有一个权值。有m组查询,每个查询给出两个点,问这两点之间的路径上有多少种不同的权值
const int N = 500010;
int n, m, block;
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 num[N];
bool 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 fa, int d)
{
    int tot = 0;
    for(int i = head[v]; i != -1; i = g[i].next)
    {
        int u = g[i].to;
        if(u == fa) continue;
        dep[u] = dep[v] + 1, par[u][0] = v;
        tot += dfs(u, v, d+1);
        if(tot >= block) //分块
        {
            while(tot--) pos[st[--top]] = tag;
            tag++;
        }
    }
    st[top++] = v; //储存待分块的序列
    return tot + 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(--num[arr[v]] == 0) tmp--;
    }
    else if(++num[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);
    block = (int)sqrt(n);
    dfs(1, 0, 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 + (!num[arr[lca]]);//对lca特殊处理
    }
    for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
}
int main()
{
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值