牛客练习赛105
 练习赛105  
A 切蛋糕的贝贝
题目描述
贝贝有一块正 \(n\) 边形的蛋糕,他想将其分为 \(6\) 块,使得面积之比为$ 1:1:4:5:1:4$ (顺序可以打乱)。每一刀存在以下两种切法:
\(1.\) 切线为经过多边形的重心(其外接圆的圆心)的对角线
\(2.\) 切线为多边形的重心与其顶点的连线
贝贝想知道最少需要的刀数,但是他是个菜狗,以至于他无法解决这个问题,所以他找到了一个大佬(也就是你)帮忙解决。
思路
只有 \(n\) 能够被 \(1 + 1 + 4 + 5 + 1 + 4\) 整除时才会有答案。
    int x; std::cin >> x;
	std::cout << (x % 16 ? -1 : x / 16 * 5) << "\n";
B 抱歉,这没有集美
题目描述
有 \(n\) 个学生编号为 \(1\sim n\) ,现在分给他们每一人一个按 \(1\sim n\) 编号的球,第 \(i\) 个学生分到的球得编号为 \(p_i\) ,如果存在有学生自己得编号和手中得球得编号的最大公约数 \(\gcd\left(i, p_i\right)\) 为偶数,就是一个好的分法。现在问有多少种好的分法。
思路:
如果正向去考虑有多少种分法的话,需要从 \(\left\lfloor\dfrac{n}{2}\right\rfloor\) 个偶数种选出来 \(1\) 个来安放在所有的偶数的位置上的方案,也就是 \(\dbinom{\left\lfloor\dfrac{n}{2}\right\rfloor}{1} * \dbinom{\left\lfloor\dfrac{n}{2}\right\rfloor}{1} * A^{n - 1}_{n - 1}\) 但是这样的算法的话会有重复的选法被记录进去,所以正向很难算出来。那么就考虑正难则反的方法,让所有的奇数都占据在偶数的位置上,那么就是 \(A^{\left\lfloor\dfrac{n}{2}\right\rfloor}_{\left\lceil\dfrac{n}{2}\right\rceil} * A^{\left\lceil\dfrac{n}{2}\right\rceil}_{\left\lceil\dfrac{n}{2}\right\rceil}\) ,所以最终的答案就是用总共的 \(A^{n}_{n}\) 减去这个值就行了。注意用逆元去计算
    CNM::init();
    int n; std::cin >> n;
    int t = n / 2 + (n & 1);
    std::cout << CNM::fac[n] - CNM::fac[t] * CNM::fac[t] << "\n";
C 打牌的贝贝
题目描述:
贝贝和宁宁玩一种卡牌游戏,一共有 \(2n\) 张牌,每个数介于 \(1\sim 2n\) 之间且各不相同,每个人一开始分别有 \(n\) 张牌。规则是:贝贝先出一张牌,宁宁要出一张比贝贝所给出的牌点数大的牌,不然宁宁输;如果最后所有的牌都出完了就是贝贝输。求两人获胜的方案有多少种。
思路:
因为贝贝每出一张牌宁宁都要出一张比这个数大的牌,所以相当于一个匹配的问题,把贝贝出的牌看作左括号,宁宁出的牌看作右括号,如果最终是一个合法的括号序列,就是宁宁赢否则贝贝赢。而合法括号序列数可以用卡特兰数 \(Catalan_n\) 在 \(O\left(n\right)\) 的时间复杂度下计算得出。
    void solve() {
        int n;
        std::cin >> n;
        Z ans = binom(2 * n, n) * Z(n + 1).inv() * power(Z(1), n);
        std::cout << binom(2 * n, n) - ans << " " << ans << "\n";
    }
卡特兰数计算的公式:
D 点分治分点
题目描述:
给定一个 \(n\) 个点, \(m\) 条带权的边的有向图,定义一条简单路径的 \(low\) 值为其路径上的边权的最小值,\(d(u, v)\) 为从 \(u\) 到 \(v\) 所有简单路径的最大 \(low\) 值。注意,简单路径不能包含两个相同的点,故恒有 \(d(u,u)=-1\) .
对于给定的 \(s\) ,\(u\) 从 \(1\) 到 \(n\) 输出 \(d(s,u)\) ,如果没有任何一条简单路径则输出 \(-1\) 。
思路:
首先观察到是有向图上求两点之间路径上边权最小化的最大值,\(Kruskal\) 重构树就用不上了。考虑到直接统计从 \(s\) 到 \(x\) 点的答案,不太好更新状态,所以也不能直接从起点开始遍历,那么就考虑用最小生成树的思想 ,按照边权从大到小的顺序来枚举所有的边,看这条边是不是与 \(s\) 所在的集合相连,是的话就保留这一条边,否则删去它;这样就保证了在最后剩余的边,就是我们想要得到的答案。
    int n, m, s;
    std::cin>> n >> m >> s;
    std::vector<std::vector<std::array<int, 2>>> adj(n + 1);
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        std::cin >> u >> v >> w;
        adj[u].push_back({v, w});
    }
    vector<int> dist(n + 1);
    auto dijkstra = [&]() {
        std::vector<bool> vis(n + 1);
        dist[s] = 1 << 30;
        priority_queue<pair<int, int>> heap;
        heap.push(make_pair(dist[s], s));
        while (heap.size()) {
            std::pair<int, int> t = heap.top();heap.pop();
            int ver = t.second, distance = t.first;
            if (vis[ver]) continue;
            vis[ver] = true;
            for (auto& G : adj[ver]) {
                int v = G[0], w = G[1];
                if (dist[v] < std::min(dist[ver], w)) //更新low值
                    dist[v] = std::min(dist[ver], w), heap.push(make_pair(dist[v], v));
            }
        }
    };
    dijkstra();
    for (int i = 1; i <= n; i++) {
        if (i == s || dist[i] == 0) dist[i] = -1;
        std::cout << dist[i] << " ";
    }

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号