树上启发式合并

独自空忆成欢 提交于 2020-12-06 18:58:32

强大的解决对于子树的询问问题。

有个英文名字叫 dsu on tree。

一种是基于重链剖分的,一种是基于set map的启发式合并的。

第一种就是先走轻边,再走重边,重边的不改回来,这样修改次数就是 $logn$ 次的。

第二种就是看set map的size,把size小的并到大的上。

某些情况下!!!dfs序+主席树(某种数据结构)比这个做法可能少一个log!!!

 

600E - Lomsat gelral

模板题啦

#include <bits/stdc++.h>
#define ll long long

const int N = 1e5 + 7;

int n, cc[N], col[N], son[N], sz[N], skip[N];
ll ans[N];
std::vector<int> G[N];

void dfs1(int u, int fa = 0) {
    sz[u] = 1;
    for (auto v: G[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}

ll sum;
int cx;

void edt(int u, int fa, int k) {
    cc[col[u]] += k;
    if (k > 0 && cc[col[u]] >= cx) {
        if (cc[col[u]] > cx) 
            sum = 0, cx = cc[col[u]];
        sum += col[u];
    }
    for (auto v: G[u])
        if (v != fa && !skip[v])
            edt(v, u, k);
}

void dfs(int u, int fa = 0, bool kep = 0) {
    for (auto v: G[u]) 
        if (v != fa && v != son[u])
            dfs(v, u);
    if (son[u])
        dfs(son[u], u, 1), skip[son[u]] = 1;
    edt(u, fa, 1);
    ans[u] = sum;
    if (son[u])
        skip[son[u]] = 0;
    if (!kep)
        edt(u, fa, -1), cx = sum = 0;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) 
        scanf("%d", col + i);
    for (int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs1(1);
    dfs(1);
    for (int i = 1; i <= n; i++)
        printf("%lld%c", ans[i], " \n"[i == n]);
    return 0;
}
View Code

 

570D - Tree Requests

把询问离线,一些字母能组成回文串,即出现偶数次的字母不能超过一个,那么对每一个深度用一个26位的二进制数表示一个字母出现的奇偶性,记为 $cnt[dep]$ 即可。

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second

const int N =  5e5 + 7;
std::vector<int> G[N];
std::vector<std::pii> que[N];
int cnt[N], n, m, dep[N], sz[N], son[N];
char s[N];
bool ans[N], skip[N];

void dfs1(int u, int d) {
    dep[u] = d;
    sz[u] = 1;
    for (auto v: G[u]) {
        dfs1(v, d + 1);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}

void edt(int u) {
    cnt[dep[u]] ^= (1 << (s[u] - 'a'));
    for (auto v: G[u]) 
        if (!skip[v]) edt(v);
}

bool check(int d) {
    int res = 0;
    for (int i = 0; i < 26; i++)
        if (cnt[d] >> i & 1)
            res++;
    return res <= 1;
}

void dfs(int u, int keep = 0) {
    for (auto v: G[u])
        if (v != son[u]) dfs(v, 0);
    if (son[u])
        dfs(son[u], 1), skip[son[u]] = 1;
    edt(u);
    for (auto p: que[u])
        ans[p.se] = check(p.fi);
    if (son[u]) skip[son[u]] = 0;
    if (!keep)
        edt(u);
}

int main() {
//    freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    for (int i = 2; i <= n; i++) {
        int x;
        scanf("%d", &x);
        G[x].push_back(i);
    }
    scanf("%s", s + 1);
    for (int i = 1, u, h; i <= m; i++) {
        scanf("%d%d", &u, &h);
        que[u].push_back(std::pii(h, i));
    }
    dfs1(1, 1);
    dfs(1);
    for (int i = 1; i <= m; i++)
        puts(ans[i] ? "Yes" : "No");
    return 0;
}
View Code

 

Sgu507

想用重链剖分的写法。用一个multiset维护出现的数字,一个multiset维护每个插入的数和附近两个数的差值.

但是这跟插入顺序有关,插入的时候正着插入,删除的时候倒着删除,然后WA on 20了... 正着删除有RE on 20了...

看了一下别人的解法就是,每个结点维护一个set,发现这个set元素个数最多是所有叶子个数,空间能接受,然后就启发式合并就OK了。

#include <bits/stdc++.h>

namespace IO {
    void read() {}
    template <typename T, typename... T2>
    inline void read(T &x, T2 &... oth) {
        T f = 1; x = 0;
        char ch = getchar();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
        while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getchar(); }
        x *= f;
        read(oth...);
    }
} // using namespace IO
#define read IO::read
#define print IO::print
#define flush IO::flush

const int N = 1e5 + 7;
const int INF = 2147483647;
std::vector<int> vec[N];
std::set<int> st[N];
int n, m, color[N], ans[N];

int merge(int u, int v) {
    if (st[u].size() < st[v].size())
        std::swap(st[u], st[v]);
    int res = INF;
    for (std::set<int>::iterator it = st[v].begin(); it != st[v].end(); it++) {
        auto i = st[u].lower_bound(*it);
        if (i == st[u].begin()) {
            res = std::min(res, *i - *it);
        } else if (i == st[u].end()) {
            i--;
            res = std::min(res, *it - *i);
        } else {
            res = std::min(res, *i - *it);
            i--;
            res = std::min(res, *it - *i);
        }
        st[u].insert(*it);
    }
    st[v].clear();
    return res;
}

void dfs(int u) {
    ans[u] = INF;
    if (vec[u].size() == 0) {
        st[u].insert(color[u]);
        return;
    }
    for (int v: vec[u]) {
        dfs(v);
        ans[u] = std::min(ans[u], ans[v]);
        ans[u] = std::min(ans[u], merge(u, v));
    }
}

int main() {
    read(n, m);
    for (int i = 2; i <= n; i++) {
        int u;
        read(u);
        vec[u].push_back(i);
    }
    for (int i = n - m + 1; i <= n; i++)
        read(color[i]);
    dfs(1);
    for (int i = 1; i <= n - m; i++)
        printf("%d%c", ans[i], " \n"[i == n - m]);
    return 0;
}
View Code

 

HackerEarth, The Grass Type

这个就是每个结点维护一个map表示其子树钟出现过的数字以及出现的次数。

然后根据合并前枚举小的map里面的每个元素,看看大的map中能与其相乘成为当前结点的值的就行了。

#include <bits/stdc++.h>
#define ll long long

const int N = 1e5 + 7;
std::map<int, int> mp[N];
int n, val[N];
std::vector<int> vec[N];
ll ans;

void merge(int u, int v) {
    if (mp[u].size() < mp[v].size()) std::swap(mp[u], mp[v]);
    for (auto p: mp[v]) {
        if (val[u] % p.first == 0)
            ans += 1LL * p.second * mp[u][val[u] / p.first];
    }
    for (auto p: mp[v]) 
        mp[u][p.first] += p.second;
    mp[v].clear();
}

void dfs(int u, int fa) {
    mp[u][val[u]] = 1;
    for (int v: vec[u]) {
        if (v == fa) continue;
        dfs(v, u);
        merge(u, v);
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    for (int i = 1; i <= n; i++)
        scanf("%d", val + i);
    dfs(1, 0);
    printf("%lld\n", ans);
    return 0;
}
View Code

 

246E - Blood Cousins Return

对每一个深度用一个map维护出现的字符串以及出现的次数,然后然后就做完了。

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second

const int N = 1e5 + 7;
char s[N][22];
std::vector<int> vec[N];
std::vector<std::pii> query[N];
std::map<std::string, int> mp[N];
int n, son[N], sz[N], dep[N], ans[N], root, cnt[N];
bool skip[N];

void dfs1(int u, int d) {
    sz[u] = 1;
    dep[u] = d;
    son[u] = -1;
    for (int v: vec[u]) {
        dfs1(v, d + 1);
        sz[u] += sz[v];
        if (son[u] == -1 || sz[son[u]] < sz[v]) son[u] = v;
    }
}

void edt(int u, int k) {
    mp[dep[u]][s[u]] += k;
    int w = mp[dep[u]][s[u]];
    if (k == 1 && w == 1) cnt[dep[u]]++;
    if (k == -1 && w == 0) cnt[dep[u]]--;
    assert(cnt[dep[u]] >= 0);
    for (int v: vec[u])
        if (!skip[v])
            edt(v, k);
}

void dfs(int u, bool keep = 0) {
    for (int v: vec[u])
        if (v != son[u])
            dfs(v);
    if (son[u] != -1)
        dfs(son[u], 1), skip[son[u]] = 1;
    edt(u, 1);
    for (auto p: query[u]) 
        ans[p.se] = cnt[dep[u] + p.fi];
    if (son[u] != -1)
        skip[son[u]] = 0;
    if (!keep)
        edt(u, -1);
}

int main() {
    scanf("%d", &n);
    for (int i = 1, u; i <= n; i++) {
        scanf("%s%d", s[i], &u);
        vec[u].push_back(i);
    }
    int m;
    scanf("%d", &m);
    for (int i = 1; i <= m; i++) {
        int u, k;
        scanf("%d%d", &u, &k);
        query[u].push_back(std::pii(k, i));
    }
    dfs1(0, 0);
    dfs(0, 1);
    for (int i = 1; i <= m; i++)
        printf("%d\n", ans[i]);
    return 0;
}
View Code

 

208E - Blood Cousins

先转换一下询问,把 $v_i$ 往上跳 $p_i$ 步得到 $u_i$,变成询问 $u_i$ 有多少个 $p_i$ 级儿子。然后就做完了。

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second

const int N = 1e5 + 7;
int dep[N], son[N], sz[N], n, fa[N][20], ans[N];
bool skip[N];
std::vector<int> vec[N];
std::vector<std::pii> query[N];

void dfs1(int u, int pre = 0, int d = 0) {
    dep[u] = d;
    fa[u][0] = pre;
    for (int i = 1; i <= 18; i++) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
        if (!fa[u][i]) break;
    }
    sz[u] = 1;
    son[u] = -1;
    for (int v: vec[u]) {
        dfs1(v, u, d + 1);
        sz[u] += sz[v];
        if (son[u] == -1 || sz[son[u]] < sz[v])
            son[u] = v;
    }
}

int jump(int u, int k) {
    for (int i = 18; ~i; i--)
        if (k >> i & 1)
            u = fa[u][i];
    return u;
}

int cnt[N];

void edt(int u, int k) {
    cnt[dep[u]] += k;
    for (int v: vec[u])
        if (!skip[v])
            edt(v, k);
}

void dfs(int u, bool keep = 0) {
    for (int v: vec[u])
        if (v != son[u])
            dfs(v);
    if (son[u] != -1) 
        dfs(son[u], 1), skip[son[u]] = 1;
    edt(u, 1);
    for (auto p: query[u]) {
        ans[p.se] = cnt[dep[u] + p.fi] - 1;
    }
    if (son[u] != -1)
        skip[son[u]] = 0;
    if (!keep)
        edt(u, -1);
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int u;
        scanf("%d", &u);
        vec[u].push_back(i);
    }
    int m;
    scanf("%d", &m);
    dfs1(0);
    for (int i = 1; i <= m; i++) {
        int u, k;
        scanf("%d%d", &u, &k);
        u = jump(u, k);
        if (u) query[u].push_back(std::pii(k, i));
    }
    dfs(0, 1);
    for (int i = 1; i <= m; i++)
        printf("%d%c", ans[i], " \n"[i == m]);
    return 0;
}
View Code

 

291E - Tree-String Problem

这题可以用启发式合并,但是没必要。

另一个trick。当需要继承、查询祖先上的信息时,可以用一个(树状数组/桶)维护这些信息,进入一个点时加入该点的信息,离开这个点时删除这些信息即可。

比如树上斜率优化DP就可以这样做。

那么这道题就用一个vector维护哈希值,进入一个点,就继续插入哈希值,并在插入的过程中完成查询。离开时删除即可。

#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long

const ull base = 19260817;
const int N = 3e5 + 7;

ull p[N], need;
std::vector<char> s[N];
std::vector<int> vec[N];
std::vector<ull> cur;
char t[N];
ll ans;
int len, n;

void dfs(int u) {
    for (int i = 0; i < s[u].size(); i++) {
        cur.push_back(cur.back() * base + s[u][i]);
        if (cur.size() > len) {
            ull x = cur.back() - cur[cur.size() - len - 1] * p[len];
            if (x == need) ans++;
        }
    }
    for (int v: vec[u])
        dfs(v);
    for (int i = 0; i < s[u].size(); i++)
        cur.pop_back();
}

int main() {
    scanf("%d", &n);
    for (int i = p[0] = 1; i < N; i++)
        p[i] = p[i - 1] * base;
    for (int i = 2; i <= n; i++) {
        int u;
        scanf("%d%s", &u, t);
        vec[u].push_back(i);
        int len = strlen(t);
        for (int j = 0; j < len; j++)
            s[i].push_back(t[j]);
    }
    scanf("%s", t);
    len = strlen(t);
    for (int i = 0; i < len; i++)
        need = need * base + t[i];
    cur.push_back(0);
    dfs(1);
    printf("%lld\n", ans);
    return 0;
}
View Code

 

 1009F - Dominant Indices

这题是长链剖分板子。而我用了重链剖分...

$O(nlog^{2}n)$ 水过去了...

用一个pair记录 cnt[dep[u]] 和 dep[u],然后用set维护,cnt取负就能变成越大的排越前,在cnt相同时dep更小的排越前。

当set为空或者第一个的cnt是-1时答案是0

#include <bits/stdc++.h>
#define pii pair<int, int>

const int N = 1e6 + 7;
int n, cnt[N], dep[N], son[N], sz[N], cur, ans[N];
std::vector<int> vec[N];
std::set< std::pii > st;
bool skip[N];

void dfs1(int u, int fa = 0) {
    dep[u] = dep[fa] + 1;
    sz[u] = 1;
    for (int v: vec[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[son[u]] < sz[v])
            son[u] = v;
    }
}

void edt(int u, int fa, int k) {
    auto it = st.find(std::pii(-cnt[dep[u]], dep[u]));
    if (it != st.end()) st.erase(it);
    cnt[dep[u]] += k;
    st.insert(std::pii(-cnt[dep[u]], dep[u]));
    for (int v: vec[u])
        if (v != fa && !skip[v])
            edt(v, u, k);
}

void dfs(int u, int fa, bool keep = 0) {
    for (int v: vec[u])
        if (v != fa && v != son[u])
            dfs(v, u);
    if (son[u])
        dfs(son[u], u, 1), skip[son[u]] = 1;
    edt(u, fa, 1);
    if (st.empty() || st.begin()->first == -1)
        ans[u] = 0;
    else 
        ans[u] = st.begin()->second - dep[u];
    if (son[u])
        skip[son[u]] = 0;
    if (!keep)
        edt(u, fa, -1);
}

int main() {
    scanf("%d", &n);
    for (int i = 1, u, v; i < n; i++)
        scanf("%d%d", &u, &v), vec[u].push_back(v), vec[v].push_back(u);
    dfs1(1, 0);
    dfs(1, 0, 1);
    for (int i = 1; i <= n; i++)
        printf("%d\n", ans[i]);
    return 0;
}
View Code

 

BZOJ2599 - [IOI2011]Race

感谢 https://blog.csdn.net/zlttttt/article/details/78634449 这篇dsu on tree的解法。

要 $O(1)$ 继承重儿子的信息,而重儿子的信息和自己的信息的差距只为一条边的长度和一条边

那么就不实际维护实际的长度和深度,而是维护一个差值。

具体用一个 cur_dep 和 cur_dis 维护当前结点 $u$ 到重链底端经过的边数和距离。

然后map维护当前节点到子树中的 节点的距离 减去 cur_dis 需要用的最小 边数 减去 cur_dep

查询是否存在一个距离时得减去 cur_dis,更新答案是加上 cur_dep 即可。

#include <bits/stdc++.h>

namespace IO
{
    char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
    int p, p3 = -1;
    void read() {}
    void print() {}
    inline int getc() {
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void flush() {
        fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
    }
    template <typename T, typename... T2>
    inline void read(T &x, T2 &... oth) {
        T f = 1; x = 0;
        char ch = getc();
        while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getc(); }
        while (isdigit(ch)) { x = x * 10 + ch - 48; ch = getc(); }
        x *= f;
        read(oth...);
    }
    template <typename T, typename... T2>
    inline void print(T x, T2... oth) {
        if (p3 > 1 << 20) flush();
        if (x < 0) buf2[++p3] = 45, x = -x;
        do {
            a[++p] = x % 10 + 48;
        } while (x /= 10);
        do {
            buf2[++p3] = a[p];
        } while (--p);
        buf2[++p3] = hh;
        print(oth...);
    }
} // using namespace IO
#define read IO::read
#define print IO::print
#define flush IO::flush
#define pii pair<int, int>
#define fi first
#define se second

inline void checkmin(int &a, int b) { if (a > b) a = b; }

const int N = 2e5 + 7;
const int INF = 0x3f3f3f3f;
int n, dep[N], son[N], sz[N], in[N], out[N], dfn[N], tol, dis[N], ans;
int cur_dis[N], cur_dep[N], k, wt[N];
std::vector<std::pii> vec[N];
std::unordered_map<int, int> f;

void dfs1(int u, int fa = 0) {
    dfn[in[u] = ++tol] = u;
    sz[u] = 1;
    for (auto p: vec[u]) {
        int v = p.fi;
        if (v == fa) continue;
        dep[v] = dep[u] + 1;
        dis[v] = dis[u] + p.se;
        wt[v] = p.se;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
    out[u] = tol;
}

void dfs(int u, int fa) {
    if (!son[u]) {
        cur_dis[u] = cur_dep[u] = 0;
        return;
    }
    for (auto p: vec[u]) 
        if (p.fi != fa && p.fi != son[u])
            dfs(p.fi, u);
    dfs(son[u], u);
    cur_dis[u] = cur_dis[son[u]] + wt[son[u]];
    cur_dep[u] = cur_dep[son[u]] + 1;
    if (f.count(f[wt[son[u]] - cur_dis[u]]))
        checkmin(f[wt[son[u]] - cur_dis[u]], 1 - cur_dep[u]);
    else 
        f[wt[son[u]] - cur_dis[u]] = 1 - cur_dep[u];
    for (auto p: vec[u]) {
        if (p.fi == fa || p.fi == son[u]) continue;
        for (int j = in[p.fi]; j <= out[p.fi]; j++) {
            int v = dfn[j];
            int temp = k - (dis[v] - dis[u]);
            if (temp > 0 && f.count(temp - cur_dis[u]))
                checkmin(ans, f[temp - cur_dis[u]] + cur_dep[u] + dep[v] - dep[u]);
        }
        for (int j = in[p.fi]; j <= out[p.fi]; j++) {
            int v = dfn[j];
            int temp = dis[v] - dis[u];
            if (temp <= k)
                if (f.count(temp - cur_dis[u]))
                    checkmin(f[temp - cur_dis[u]], dep[v] - dep[u] - cur_dep[u]);
                else 
                    f[temp - cur_dis[u]] = dep[v] - dep[u] - cur_dep[u];
        }
    }
    if (f.count(k - cur_dis[u]))
        checkmin(ans, f[k - cur_dis[u]] + cur_dep[u]);
    if (son[fa] != u && u != 1)
        f.clear();
}

int main() {
    freopen("in.txt", "r", stdin);
    read(n, k);
    for (int i = 1; i < n; i++) {
        int u, v, w;
        read(u, v, w);
        u++, v++;
        vec[u].push_back(std::pii(v, w));
        vec[v].push_back(std::pii(u, w));
    }
    ans = INF;
    dfs1(1, 0);
    dfs(1, 0);
    printf("%d\n", ans == INF ? -1 : ans);
    return 0;
}
View Code

 

375D - Tree and Queries

查询子树中颜色出现次数不少于 $k_i$ 次的颜色种类数,用一个 $cnt$ 数组存颜色出现的次数,再用一个树状数组存 $cnt$ 值的出现次数。

回答询问就是一个后缀和

#include <bits/stdc++.h>

#define pii pair<int, int>
#define fi first
#define se second

const int N = 1e5 + 7;

int color[N], cnt[N], n, m, sz[N], son[N], ans[N];
bool skip[N];
std::vector<int> vec[N];
std::vector<std::pii> query[N];

struct BIT {
    int tree[N];
    inline int lowbit(int x) {
        return x & -x;
    }
    void add(int x, int k) {
        if (!x) return;
        for (int i = x; i < N; i += lowbit(i))
            tree[i] += k;
    }
    int query(int x) {
        int ans = 0;
        for (int i = x; i; i -= lowbit(i))
            ans += tree[i];
        return ans;
    }
} bit;

void dfs1(int u, int fa) {
    sz[u] = 1;
    for (int v: vec[u]) {
        if (v == fa) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}

void edt(int u, int fa, int k) {
    bit.add(cnt[color[u]], -1);
    cnt[color[u]] += k;
    bit.add(cnt[color[u]], 1);
    for (int v: vec[u])
        if (v != fa && !skip[v])
            edt(v, u, k);
}

void dfs(int u, int fa, bool kep) {
    for (int v: vec[u]) 
        if (v != fa && v != son[u])
            dfs(v, u, 0);
    if (son[u])
        dfs(son[u], u, 1), skip[son[u]] = 1;
    edt(u, fa, 1);
    for (auto p: query[u])
        ans[p.se] = bit.query(N - 1) - bit.query(p.fi - 1);
    if (son[u])
        skip[son[u]] = 0;
    if (!kep)
        edt(u, fa, -1);
}

int main() {
    freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", color + i);
    for (int i = 1; i < n; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    for (int i = 1; i <= m; i++) {
        int u, k;
        scanf("%d%d", &u, &k);
        query[u].push_back(std::pii(k, i));
    }
    dfs1(1, 0);
    dfs(1, 0, 1);
    for (int i = 1; i <= m; i++)
        printf("%d\n", ans[i]);
    return 0;
}
View Code

 

716E - Digit Tree

查询有多少点对 $(u, v)$ 路径上的数字组成的数为 $M$ 的倍数。

点分治做的话,维护一个当前重心到当前点 $u$ 走过来组成的数字 $down_u$ 以及从 $u$ 走到重心组成的数字 $up_u$。

然后就是 $up_u * 10^{dep_v}+ down_v \equiv 0$($mod$ $M$)

得到 $up_u  \equiv M - down_v * 10^{-dep_v}$($mod$ $M$)

用两个map维护这个东西就好了。

考虑重链剖分的做法,就是先递归轻儿子,再递归重儿子。

现在 $down_u$ 和 $up_u$ 分别代表根节点到 $u$ 和 $u$ 到根节点形成的两个数字。

考虑现在到了 $f$ 节点,其子树中一个节点 $u$ 和一个节点 $v$ 能形成一个合法的点对

从 $u$ 到 $v$ 需符合

$(up_u - up_f) * 10^{-dep_f}*10^{dep_v-dep_f} + (down_v - down_f * 10^{dep_v-dep_f}) \equiv 0$($mod$ $M$)
得到 $down_f * 10^{-dep_f}+(up_f-up_u)*10^{-dep_f} \equiv down_v * 10^{-dep_v}$($mod$ $M$)

从 $v$ 到 $u$ 需符合

$down_f*10^{dep_f}-down_{u}*10^{2dep_f-dep_u}+up_f \equiv up_v$($mod$ $M$)

用两个map维护右边两个值即可。

两种做法复杂度都是 $O(nlog^2n)$,但是常数有很大区别...

#include <bits/stdc++.h>
#define ll long long
#define pii pair<int, int>
#define fi first
#define se second

const int N = 1e5 + 7;

int n, MOD, down[N], up[N], dep[N], son[N], sz[N], in[N], out[N], dfn[N], tol;
int base[N * 5];
std::vector<std::pii> vec[N];

int qp(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1LL * ans * a % MOD;
        a = 1LL * a * a % MOD;
        b >>= 1;
    }
    return ans;
}

void dfs1(int u, int fa, int fac, int p1, int p2, int d) {
    sz[u] = 1;
    dep[u] = d;
    in[u] = ++tol;
    dfn[tol] = u;
    down[u] = p1, up[u] = p2;
    for (auto p: vec[u]) {
        int v = p.fi;
        if (v == fa) continue;
        dfs1(v, u, fac * 10LL % MOD, (p1 * 10LL + p.se) % MOD, (p2 + 1LL * p.se * fac) % MOD, d + 1);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
    out[u] = tol;
}

std::map<int, int> mp1, mp2;

inline int BASE(int x) {
    return base[x + 3 * N];
}

ll ans;

void update(int u, int f) {
    int x = (1LL * down[f] * BASE(dep[f]) % MOD - 1LL * down[u] * BASE(2 * dep[f] - dep[u]) % MOD + up[f]) % MOD;
    x = (x + MOD) % MOD;
    std::map<int, int>::iterator it;
    it = mp1.find(x);
    if (it != mp1.end()) ans += it->se;
    x = (1LL * down[f] * BASE(-dep[f]) % MOD + 1LL * (up[f] - up[u]) * BASE(-2 * dep[f])) % MOD;
    x = (x + MOD) % MOD;
    it = mp2.find(x);
    if (it != mp2.end()) ans += it->se;
}

void getans(int u, int f) {
    for (int i = in[u]; i <= out[u]; i++)
        update(dfn[i], f);
}

void add(int u) {
    for (int i = in[u]; i <= out[u]; i++) {
        int f = dfn[i];
        mp1[up[f]]++;
        int x = 1LL * down[f] * BASE(-dep[f]) % MOD;
        mp2[x]++;
    }
}

void dfs(int u, int fa) {
    if (son[u]) {
        for (auto p: vec[u])
            if (p.fi != fa && p.fi != son[u])
                dfs(p.fi, u), mp1.clear(), mp2.clear();
        dfs(son[u], u);
        for (auto p: vec[u]) 
            if (p.fi != fa && p.fi != son[u])
                getans(p.fi, u), add(p.fi);
        update(u, u);
    }
    int x = 1LL * down[u] * BASE(-dep[u]) % MOD;
    mp2[x]++;
    mp1[up[u]]++;
}

int main() {
    scanf("%d%d", &n, &MOD);
    for (int i = 1; i < n; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        u++; v++;
        vec[u].push_back(std::pii(v, w));
        vec[v].push_back(std::pii(u, w));
    }
    if (MOD == 1) {
        printf("%lld\n", 1LL * n * (n - 1));
        return 0;
    }
    int phi = MOD, temp = MOD;
    for (int i = 2; i * i <= temp; i++) {
        if (temp % i == 0) {
            phi = phi / i * (i - 1);
            while (temp % i == 0) temp /= i;
        }
    }
    if (temp > 1) phi = phi / temp * (temp - 1);
    phi--;
    base[3 * N] = 1;
    int inv = qp(10, phi);
    for (int i = 1; i <= 2 * n; i++)
        base[i + 3 * N] = base[i + 3 * N - 1] * 10LL % MOD;
    for (int i = 1; i <= 2 * n; i++)
        base[3 * N - i] = 1LL * base[3 * N - i + 1] * inv % MOD;
    dfs1(1, 0, 1, 0, 0, 0);
    dfs(1, 0);
    printf("%lld\n", ans);
    return 0;
}
View Code

 

741D - Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

看错题了之后不会写。

要求每个子树内最长的回文路径,回文路径定义为路径上的字符重排后能得到回文串。

那么就如同570D - Tree Requests

用一个二十六进制的数来表示一条路径上的字符,能成为回文路径就是该数字二进制下有不超过两个 $1$。

开一个桶 $f[x]$ 表示路径为 $x$ 最长是多少。先走重儿子,再枚举轻儿子,先更新,后合并信息。

#include <bits/stdc++.h>
#define pii pair<int, int>
#define fi first
#define se second

const int N = 5e5 + 7;
int n, sz[N], son[N], pre[N], fa[N], ans[N], in[N], out[N], dfn[N], tol, dep[N];
std::vector<std::pii> vec[N];
inline void checkmax(int &a, int b) { if (a < b) a = b; }
int f[1 << 22];

void dfs1(int u, int f) {
    sz[u] = 1;
    dfn[in[u] = ++tol] = u;
    dep[u] = dep[f] + 1;
    for (auto p: vec[u]) {
        int v = p.fi;
        if (v == f) continue;
        fa[v] = u;
        pre[v] = pre[u] ^ (1 << p.se);
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[son[u]] < sz[v])
            son[u] = v;
    }
    out[u] = tol;
}

void dfs(int u, bool kep) {
    for (auto p: vec[u]) 
        if (p.fi != son[u])
            dfs(p.fi, 0), checkmax(ans[u], ans[p.fi]);
    if (son[u])
        dfs(son[u], 1), checkmax(ans[u], ans[son[u]]);
    if (f[pre[u]]) checkmax(ans[u], f[pre[u]] - dep[u]);
    for (int i = 0; i < 22; i++)
        if (f[pre[u] ^ (1 << i)]) checkmax(ans[u], f[pre[u] ^ (1 << i)] - dep[u]);
    checkmax(f[pre[u]], dep[u]);
    for (auto p: vec[u]) 
        if (p.fi != son[u]) {
            for (int i = in[p.fi]; i <= out[p.fi]; i++) {
                int v = dfn[i];
                if (f[pre[v]]) checkmax(ans[u], f[pre[v]] + dep[v] - 2 * dep[u]);
                for (int i = 0; i < 22; i++)
                    if (f[pre[v] ^ (1 << i)]) checkmax(ans[u], f[pre[v] ^ (1 << i)] + dep[v] - 2 * dep[u]);
            }
            for (int i = in[p.fi]; i <= out[p.fi]; i++) {
                int v = dfn[i];
                checkmax(f[pre[v]], dep[v]);
            }
        }
    
    if (!kep)
        for (int i = in[u]; i <= out[u]; i++)
            f[pre[dfn[i]]] = 0;
}

int main() {
    scanf("%d", &n);
    for (int i = 2; i <= n; i++) {
        int p;
        char s[3];
        scanf("%d%s", &p, s);
        vec[p].push_back(std::pii(i, (int)(s[0] - 'a')));
    }
    dfs1(1, 0);
    dfs(1, 1);
    for (int i = 1; i <= n; i++)
        printf("%d%c", ans[i], " \n"[i == n]);
    return 0;
}
View Code

 

完结撒花!!!

https://codeforces.com/blog/entry/44351

评论区还有很多题,慢慢补吧...

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!