• 离线赛 2019.10.29


    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], " 
    "[i == n]);
        rep (i, 1, n) printf("%d%c", gt[i][m], " 
    "[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 ]

  • 相关阅读:
    CentOS6 配置阿里云 NTP 服务
    使用docker-compose运行nginx容器挂载时遇到的文件/目录问题
    Springboot配置文件参数使用docker-compose实现动态配置
    Dockerfile文件全面详解
    docker 生成mysql镜像启动时自动执行sql
    CentOS无法识别NTFS格式U盘完美解决方案
    网络模型与网络策略
    k8s更换网络插件:从flannel更换成calico
    数据采集实战(四)-- 线性代数习题答案下载
    leedcode 146. LRU 缓存机制(哈希+双向链表)
  • 原文地址:https://www.cnblogs.com/Ameiyo/p/11765731.html
Copyright © 2020-2023  润新知