hackerrank Week of Code 34 Path Statistics

题面在这里

题意:

给一棵树,点上有点权,每次询问u,v,k,表示询问u-v路径上出现次数第k多的点权,如果次数一样则点权越小当做次数越多。

做法:

首先肯定要树上莫队。

然后我们考虑怎么求出现次数的第k小。
发现我们要求的其实是一个二元组(次数,点权)的第k小。
于是可以考虑在这个序列上分块。
预处理出这个二元组排完序后的编号,然后分成根号块。
记录一个bsum[i]表示这一块内出现过的点的数量,sum[i]表示这个位置出现的点的数量。
查询的时候从右往左扫每一个块,查到某一个块再在块内查询。
注意修改一个节点的存在情况时有一些细节要注意。
复杂度nsqrt(n).

易错点:

(只有我会犯的易错点请忽略)
就是离散化的时候下标更改过了,之后要用更改后的下标范围(代码中的num_tot),否则会re的很惨。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cctype>
#include<cmath>
#include<vector>
#define pb push_back
#define debug puts("orz")
using namespace std;
typedef long long ll;

inline ll read() {
    char ch = getchar(); ll x = 0; int op = 1;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') op = -1;
    for(; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
    return x*op;
}
inline void write(ll a) {
    if(a < 0) putchar('-'), a = -a;
    if(a >= 10) write(a/10); putchar('0'+a%10);
}

const int N = 50010;
const int inf = 1e9;
int n, m, num_tot, tot, bsz, cnt, clk;
int a[N], b[N], num[N], head[N], f[N][25], depth[N], sz[N], bel[N], pos[N], bl[N], br[N], sum[N], bsum[N], tong[N], ans[N];
vector<int> id[N];
struct edge {
    int to, nxt;
    edge() {}
    edge(int x, int y) { to = x, nxt = y; }
}e[N<<1];
struct questions { int x, y, k, id; }q[N];
struct node { int a, b; node() {} node(int x, int y) { a = x, b = y; } }qu[N];

const bool cmp(const questions &x, const questions &y) {
    return bel[x.x] < bel[y.x] || bel[x.x] == bel[y.x] && pos[x.y] < pos[y.y];
}
const bool cmpnode(const node &x, const node &y) { return x.b < y.b || x.b == y.b && x.a < y.a; }
inline void addedge(int x, int y) { e[++ cnt] = edge(y, head[x]); head[x] = cnt; }
inline void dfs(int u, int lst, int s) {
    pos[u] = ++ clk;
    if(sz[tot] == bsz) sz[bel[u] = ++ tot] = 1; else sz[bel[u] = tot] ++;
    depth[u] = s; f[u][0] = lst;
    for(int i = head[u]; i; i = e[i].nxt) if(e[i].to != lst) dfs(e[i].to, u, s+1);
}
inline int lca(int x, int y) {
    if(depth[x] < depth[y]) swap(x, y);
    int tmp = depth[x] - depth[y];
    for(int i = 16; i >= 0; i --) if(tmp>>i&1) x = f[x][i];
    if(x == y) return x;
    for(int i = 16; i >= 0; i --)
        if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
inline void work(int x) {//改变节点x的状态
    int c = a[x], i;//c颜色
    if(b[x]) {
        //删掉当前节点的颜色
        i = id[c][tong[c]];//tong[c]是当前c颜色有几个
        sum[i] --; bsum[bel[i]] --; tong[c] --;
        if(tong[c]) {
            i = id[c][tong[c]];
            sum[i] ++; bsum[bel[i]] ++;
        }
        b[x] = 0;
    } else {
        //插入一个颜色为c的节点
        if(tong[c]) {
            i = id[c][tong[c]];
            sum[i] --; bsum[bel[i]] --;
        }
        tong[c] ++; i = id[c][tong[c]];
        sum[i] ++; bsum[bel[i]] ++;
        b[x] = 1;
    }
}
inline void change(int x, int y) { while(x != y) { work(x); x = f[x][0]; } }
inline int qry(int k) {
    for(int i = tot; i >= 1; i --) if(bsum[i] >= k) {
        for(int j = br[i]; j >= bl[i]; j --) if(sum[j]) {
            if(k == 1) return num[qu[j].a];
            k -= sum[j];
        }
    } else k -= bsum[i];
}
int main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    n = read(), m = read();
    for(int i = 1; i <= n; i ++) a[i] = num[i] = read();
    sort(num+1, num+1+n); num_tot = unique(num+1, num+1+n)-num-1;
    for(int i = 1; i <= n; i ++) {
        a[i] = lower_bound(num+1, num+1+num_tot, a[i])-num;//这里注意不要写成num+1+n!
        tong[a[i]] ++;
    } int tail = 0;
    for(int i = 1; i <= num_tot; i ++) {
        id[i].pb(inf);
        for(int j = 1; j <= tong[i]; j ++) id[i].pb(0), qu[++ tail] = node(i, j);
    }
    sort(qu+1, qu+1+tail, cmpnode);
    for(int i = 1; i <= tail; i ++) id[qu[i].a][qu[i].b] = i;//预处理一个二元组(次数,颜色)排完序的编号
    for(int i = 1; i < n; i ++) {
        int x = read(), y = read();
        addedge(x, y); addedge(y, x);
    } bsz = sqrt(n); dfs(1, 0, 0);//树分块、倍增预处理
    for(int j = 1; j <= 16; j ++)
        for(int i = 1; i <= n; i ++) f[i][j] = f[f[i][j-1]][j-1];
    for(int i = 1; i <= m; i ++) {
        q[i].x = read(), q[i].y = read(), q[i].k = read();
        q[i].id = i;
    } sort(q+1, q+1+m, cmp);//查询预处理
    //在(次数,颜色)这个二元组序列上分块
    for(int i = 1; i <= tot; i ++) { bl[i] = br[i-1]+1; br[i] = br[i-1]+bsz; }
    br[tot] = n;
    for(int i = 1; i <= tot; i ++)
        for(int j = bl[i]; j <= br[i]; j ++) bel[j] = i;
    //以上是分块预处理
    memset(tong, 0, sizeof tong);
    int l = 1, r = 1;
    for(int i = 1; i <= m; i ++) {
        int x = q[i].x, y = q[i].y, z;
        z = lca(l, x); change(l, z); change(x, z);
        z = lca(r, y); change(r, z); change(y, z);
        z = lca(x, y); work(z);
        ans[q[i].id] = qry(q[i].k);
        work(z); l = x, r = y;
    }
    for(int i = 1; i <= m; i ++) write(ans[i]), puts("");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值