CF1111E Tree

Posted venividivici

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,Qleq 10^5,sum k_ileq 10^5)


解法

考虑 DP

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

(f[i][j]) 为把前 (i) 个点分为 (j) 组的方案数,那么有以下转移:
[ f[i][j]=f[i-1][j-1]+f[i-1][j] imes 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
", 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;   
}

以上是关于CF1111E Tree的主要内容,如果未能解决你的问题,请参考以下文章

CF1111E Tree

CF1111E Tree 树链剖分,DP

Codeforces 1111E Tree 虚树 + dp

CF570D:Tree Requests

CF 570 D. Tree Requests

「CF911F」Tree Destruction