CF1111E Tree

时间:2019-10-20
本文章向大家介绍CF1111E Tree,主要包括CF1111E Tree使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题意

给定一大小为 \(N\) 的树,共有 \(Q\) 次询问

\(i\) 次询问包含了三个数 \(k_i,m_i,r_i\),接着给定了树上互不相同的 \(k_i\) 个关键点 \(a_{i,1},a_{i,2},a_{i,3}...a_{i,k}\)。对于第 \(i\) 次询问,你需要回答当这颗树以 \(r_i\) 为根时,有多少种方案将这 \(k_i\) 个点分为至多 \(m_i\) 组,使得同一组内的任意两个不同弄的结点都不存在祖先关系

对于第 \(k\) 次询问,假设你一共将 \(k_i\) 个点分为了 \(p\) 组,那么分组的方案需要满足:

  1. 给出的 \(k_i\) 个点中每个点属于且仅属于 \(p\) 组中的任意一组
  2. \(p\) 组中的任意一组非空

\(N \leq 10^5,Q\leq 10^5,\sum k_i\leq 10^5\)


解法

考虑 DP

把所有关键点按照一定顺序排好,使得点 \(i\) 的状态被更新当且仅当 \(i\) 的祖先的状态都已经被更新

\(f[i][j]\) 为把前 \(i\) 个点分为 \(j\) 组的方案数,那么有以下转移:
\[ f[i][j]=f[i-1][j-1]+f[i-1][j]\times max(0, j-g_i) \]
其中 \(g_i\) 指的是第 \(i\) 个点到根节点的路径上的关键点个数(即祖先关键点的个数)

这个转移实际上是第二类斯特林数的转移,只不过带了一些限制:当前的点不能与其祖先所在的集合合并(显然其祖先互相之间一定也不属于同一集合)

\(g_i\) 可以直接用树剖加树状数组维护,现在的问题就是如何安排 DP 转移的顺序了

发现按照 \(g_i\) 从小到大的顺序转移也是完全没有问题的:因为祖先的 \(g\) 一定比儿子的小(不能用 DFS 序的原因是复杂度无法保证)


代码

#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1e5 + 10;
const int mod = 1e9 + 7;

int read();
void dfs_1(int x);
void dfs_2(int x, int tp);

int N, Q;

int f[MAX_N][310];
int fa[MAX_N], sz[MAX_N], top[MAX_N], dep[MAX_N], son[MAX_N], dfn[MAX_N], cnt;

int head[MAX_N], to[MAX_N << 1], nxt[MAX_N << 1], cap;

inline void swap(int& x, int& y) { x ^= y ^= x ^= y; }
inline void inc(int& x, int y) { (x += y) >= mod ? x -= mod : 0; }
inline int mul(int x, int y) { return 1LL * x * y % mod; }

inline int min(int x, int y) { return x < y ? x : y; }
inline int max(int x, int y) { return x > y ? x : y; }

struct BIT {
    int c[MAX_N];
    void ins(int x, int v) {
        for (; x && x <= N; x += x & -x)  c[x] += v;
    }
    int query(int x, int res = 0) {
        for (; x; x -= x & -x)  res += c[x];
        return res; 
    }
} tr;

struct node { 
    int x, g;   
    bool operator < (const node& rhs) const { return g < rhs.g; }
} a[MAX_N];

inline void link(int x, int y) {
    to[++cap] = y, nxt[cap] = head[x], head[x] = cap;
    to[++cap] = x, nxt[cap] = head[y], head[y] = cap;
}

int query(int u, int v) {
    int res = 0;
    while (top[u] ^ top[v]) {
        if (dep[top[u]] < dep[top[v]])  swap(u, v);
        res += tr.query(dfn[u]) - tr.query(dfn[top[u]] - 1);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v])  swap(u, v);
    res += tr.query(dfn[u]) - tr.query(dfn[v] - 1);
    return res - 1;
}

int main() {
    
    N = read(), Q = read();
    
    for (int i = 1; i < N; ++i)  link(read(), read());
    
    dep[1] = 1;
    dfs_1(1);
    dfs_2(1, 1);
    
    int k, m, r;
    while (Q--) {
        k = read(), m = read(), r = read();
        for (int i = 1; i <= k; ++i)  tr.ins(dfn[a[i].x = read()], 1);
        for (int i = 1; i <= k; ++i)  a[i].g = query(a[i].x, r);
        
        sort(a + 1, a + k + 1);
        
        f[0][0] = 1;
        for (int i = 1; i <= k; ++i) 
            for (int j = 1; j <= m; ++j) 
                inc(f[i][j], (f[i - 1][j - 1] + mul(f[i - 1][j], max(0, j - a[i].g))) % mod);
        
        int res = 0;
        for (int i = 1; i <= m; ++i)  inc(res, f[k][i]);
        
        for (int i = 1; i <= k; ++i)  tr.ins(dfn[a[i].x], -1);
        for (int i = 1; i <= k; ++i)
            for (int j = 1; j <= m; ++j)  f[i][j] = 0;
        
        printf("%d\n", res);
    }
    
    return 0;
}

void dfs_1(int x) {
    sz[x] = 1;
    for (int i = head[x]; i; i = nxt[i]) 
        if (to[i] != fa[x]) {
            fa[to[i]] = x, dep[to[i]] = dep[x] + 1, dfs_1(to[i]), sz[x] += sz[to[i]];
            if (sz[to[i]] > sz[son[x]])  son[x] = to[i];
        }
}

void dfs_2(int x, int tp) {
    top[x] = tp, dfn[x] = ++cnt;
    if (son[x]) {
        dfs_2(son[x], tp);
        for (int i = head[x]; i; i = nxt[i])
            if (!dfn[to[i]])  dfs_2(to[i], to[i]);  
    }
}

int read() {
    int x = 0, c = getchar();
    while (!isdigit(c))  c = getchar();
    while (isdigit(c))   x = x * 10 + c - 48, c = getchar();
    return x;   
}

原文地址:https://www.cnblogs.com/VeniVidiVici/p/11707722.html