2019.10.29

\[Ameiyo \]


牛客 CSP-S 提高组赛前集训营1

  • A : 结论题,博弈
  • B : 树形 Dp ,换根
  • C : 结论题,并查集,Dfs

A

发现只有一个石头的时候先手必胜,反之后手必胜。可以递归证明(数学归纳法) 的样子

每组石头之间组合后的输赢是可以异或起来的。


B

挺水的树形 Dp ,虽然一开始状态没弄好导致很难打。

首先考虑数量。这是一个很简单的东西,看看 $ k $ 的范围就可以秒掉。

令 $ f[i][j] $ 表示 $ i $ 的子树内距离 $ i $ 小于等于 $ j $ 的点的数量,对于 $ u, v $ ,其中 $ v $ 是 $ u $ 的儿子:

\[f[u][j] = 1 + \sum f[v][j - 1] , f[u][0] = 1 \]

因为距离 $ v $ 是 $ j - 1 $ 那么距离 $ u $ 就是 $ j $ 了。

之后再换根得到对于整棵树的情况(怎么上来怎么下去,具体看代码)。

再考虑乘积。令 $ g[i][j] $ 表示 $ i $ 的子树内距离 $ i $ 小于等于 $ j $ 的点 (在只有小于等于 $ j $ 的点走到 $ i $ 的情况下) 的贡献之积(手动断句)。

同上:

\[g[u][j] = f[u][j] * \prod f[v][j - 1] , g[u][0] = f[u][0] = 1 \]

换根的时候要注意要先把子树内原来的贡献除掉,再传下去,有一点细节,看代码吧。

其实因为有取模,所以做除法的时候还要考虑没有逆元的情况。

但是因为这道题只有乘法,没有加法,而且初值不可能是模数的倍数,因此也不会出现中间出现模数的倍数的情况。

代码里的 $ ft , gt $ 分别对应树外不包括自己的情况。

注意最后合并 $ g $ 的时候,因为一开始统计的自身只包括了树内的点,而实际上自身的贡献是要包括树外的,所以要先把原来的 $ f $ 除掉,再乘上新的 $ f $ (其他点对于 $ u $ 而言都只用考虑树外或者树内一种,所以不用改变)。

int n, m;
struct EDGE {
    int to, nex;
} edge[N << 1];
int head[N], cntedge;
void Addedge(int u, int v) {
    edge[++cntedge] = (EDGE) { v, head[u] };
    head[u] = cntedge;
}
 
int f[N][M], g[N][M];
int ft[N][M], gt[N][M];
 

int Pow(int x, int k) {
    int ans = 1;
    for ( ; k > 0; x = 1ll * x * x % Mod, k >>= 1)
        ((k & 1) && (ans = 1ll * ans * x % Mod));
    return ans;
}
 
 
void Dfs(int u, int fa) {
    rep (j, 0, m) f[u][j] = 1;
    for (int i = head[u], v; i; i = edge[i].nex) {
        v = edge[i].to;
        if (v == fa) continue;
        Dfs(v, u);
        rep (j, 1, m) f[u][j] += f[v][j - 1];
    }
 
    rep (j, 0, m) g[u][j] = f[u][j];
    for (int i = head[u], v; i; i = edge[i].nex) {
        v = edge[i].to;
        if (v == fa) continue;
        rep (j, 1, m) g[u][j] = 1ll * g[u][j] * g[v][j - 1] % Mod;
    }
}
 
void Back(int u, int fa) {
    rep (j, 0, m) {
        ft[u][j] = ft[u][j] + f[u][j];
        gt[u][j] = 1ll * gt[u][j] * Pow(f[u][j], Mod - 2) % Mod;
        gt[u][j] = 1ll * gt[u][j] * ft[u][j] % Mod * g[u][j] % Mod;
    }
    for (int i = head[u], v; i; i = edge[i].nex) {
        v = edge[i].to;
        if (v == fa) continue;
        ft[v][1] = 1;
        rep (j, 1, m - 1) ft[v][j + 1] = ft[u][j] - f[v][j - 1];
 
        gt[v][0] = 1, gt[v][1] = 1;
        rep (j, 1, m - 1) gt[v][j + 1] =
            1ll * gt[u][j] * Pow(g[v][j - 1], Mod - 2) % Mod
            * Pow(ft[u][j], Mod - 2) % Mod * (ft[u][j] - f[v][j - 1]) % Mod;
        Back(v, u);
    }
}
 
int main() {
    n = read<int>(), m = read<int>();
    rep (i, 2, n) {
        int x = read<int>(), y = read<int>();
        Addedge(x, y); Addedge(y, x);
    }
 
    rep (j, 0, m) gt[1][j] = 1;
    Dfs(1, 0), Back(1, 0);

    rep (i, 1, n) printf("%d%c", ft[i][m], " \n"[i == n]);
    rep (i, 1, n) printf("%d%c", gt[i][m], " \n"[i == n]);
    return 0;
}

C

如果我们给输入的 $ A, B $ (牌的正反两面) 之间连一条边。

那么对于每条边,他只能选择一个端点。

最后形成了一个图,对于图上的每个联通块,如果是一棵树,而且没有重边,那么就做不到所有的点都被选择,因为树只有 $ n - 1 $ 条边,却又 $ n $ 个点,但是如果有了重边,或者是一个带环的图,就可以做到。

那么我们对于所有的树,找出其中最小的点 $ mi $ 和最大的点 $ mx $ ,如果 $ l, r $ 完全包含 $ mi, mx $ 那么就不行,否则一定可以做到满足。

对所有的 $ r $ 维护小于等于其的右端点中最大的左端点,这样就可以直接查询了。

int n, m, q;
int A[N], B[N];
int Fa[N], mi[N], mx[N], cnt[N], edg[N];
int MxL[N];
 
int Find(int x) { return x == Fa[x] ? x : Fa[x] = Find(Fa[x]); }
void Uni(int x, int y) {
    x = Find(x), y = Find(y);
    if (x != y) {
        Fa[x] = y, cnt[y] += cnt[x], edg[y] += edg[x] + 1;
        mi[y] = min(mi[y], mi[x]);
        mx[y] = max(mx[y], mx[x]);
    } else ++edg[y];
}
 
int main() {
    m = read<int>(), n = read<int>();
    rep (i, 1, m) Fa[i] = i, mi[i] = mx[i] = i, cnt[i] = 1;
    rep (i, 1, n) {
        A[i] = read<int>(), B[i] = read<int>();
        Uni(A[i], B[i]);
    }
    rep (i, 1, m) if (Find(i) == i) {
        int x = Fa[i];
        if (cnt[x] <= edg[x]) continue;
        MxL[mx[x]] = mi[x];
    }
    rep (i, 1, m) MxL[i] = max(MxL[i], MxL[i - 1]);
    q = read<int>();
    rep (i, 1, q) {
        int l = read<int>(), r = read<int>();
        puts(MxL[r] < l ? "Yes" : "No");
    }
    return 0;
}


\[in \ 2019.10.30 \]

 posted on 2019-10-30 16:32  Ameiyo  阅读(78)  评论(0编辑  收藏  举报