河南萌新联赛2024第(四)场题解

先给出比赛链接:
https://ac.nowcoder.com/acm/contest/88269

B 小雷的神奇电脑

求数组中任意两个下标不同的数的同或最大值

结论(1):最小异或对,一定是集合排序后相邻的数.

要证结论(1)

则要证对于任意的 a<b<c,都满足最小值一定是 abbc,而非 ac

对于 ac, 找到其二进制表示中最高的不相同的位为第 x 位,则 ac 高于 x 的位一定相同,因为 a<b<c 一定是 ax 位为 0, cx 位为 1.
因为 a<b<c 高于 x 的位 b 一定也和 a,c 都相同, b 的第 x 位可能为0或者为1.
所以 abbc 中一定有一个数第x位为0,而 acx 位为1,即一定有一个数是小于 ac 的,由此结论得证

由于a, b的同或和异或互为反码,所以最大同或对就是最小异或对

所以最大同或对,一定是集合排序后相邻的数.

下证a, b的同或和异或互为反码
对于0 1来说,显然同或和异或互为反码

那么对于一个位数为n的整数的呢

n位整数的异或和就是按位异或,同或同理

那么n位整数的异或 和 同或 一定是相反的

证毕

所以 ab+ab=2m1

Show Code B

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    vector a(n + 1);
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    sort(a.begin(),a.end());
    ll ans = (2 << (m - 1)) - 1, cur = inf;
    for (int i = 1; i < n; ++ i) {
        cur = min(cur, a[i] ^ a[i + 1]);
    }
    ans -= cur;
    cout << ans << "\n";
}




C 岗位分配

已知每个志愿者之间没有区别,有不同的多个岗位且每个岗位至少有一个人,想到用组合数学隔板法来进行计数。

fnm 为n个岗位,m个人的分配情况数(注,任意岗位可没有人)

然后即可通过隔板法求出 fnm=Cn+m1n1

下面以 f33 为例:

总共有以下情况
(3,0,0) * 3
(2,1,0) * 6
(1,1,1) * 1

构造(3,0,0)可以 123|0|0
构造(2,1,0)可以 12|3|0
构造(1,1,1)可以 1|2|3

本题每个岗位至少需要 ai 个人那么只需要把m减去 i=1nai

剩下的m就能用上面推出的 fnm 表示了

将i个候补加入岗位的贡献是 fni

将之求和即可得到答案,就是 i=0mfni

到这本题实际上已经完成了
但是还能继续简化

fnm=Cn+m1n1=Cn+m1m

i=0mfni=Cn+i1i

Cnm=Cn1m+Cn1m1

i=0mfni=Cn+mm=fn+1m   

类似的:
i=1nfim=Cn+m+1m+1=fn+1m+1

下面介绍求组合数的方法

1.杨辉三角法

Cnm=Cn1m+Cn1m1   O(n2)Cnm

2.公式法

Cnm=n!m!(nm)!   O(n)

下面给出AC代码,复杂度 O(n+m)

Show Code C

constexpr ll mod = 998244353;
ll power(ll a , ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) {
            res = res * a % mod;
        }
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
constexpr ll maxn = 10000;
ll fact[maxn + 2];
ll inv[maxn + 2];
void initfact() {
    fact[0] = 1;
    for (ll i = 1; i <= maxn; ++ i) {
        fact[i] = fact[i - 1] * i % mod;
    }
    inv[maxn] = power(fact[maxn], mod - 2);
    for (ll i = maxn - 1; i >= 1; -- i) {
        inv[i] = inv[i + 1] * (i + 1ll) % mod;
    }
}
ll C(int n, int m) {
    if (m > n) {
        return 0;
    } else if (m == 0) {
        return 1;
    } else {
        return fact[n] * inv[m] % mod * inv[n - m] % mod;
    }
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    initfact();
    ll n, m;
    cin >> n >> m;
    ll ans = 0;
    vector< ll > a(n + 1);
    for (int i = 1; i <= n; ++ i) {
        cin >> a[i];
        m -= a[i];
    }
    ans = C(n + m, m);
    cout << ans << "\n";
}




D 简单的素数

非常基础的判断质数的题
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
那只需要判断2~p-1是否存在p的因数,若存在则不是质数,若不存在则是质数,但是该题的p很大,直接这样遍历显然会超时。
优化的方法
当y(y > sqrt(p))是p的因数时,则有x = p / y < sqrt(p),且x是p的因数,所以只需要判断2~sqrt(p)+1就行

Show Code D

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    auto check = [&](int x) {
        bool flag = 1;
        for (int i = 2; i * i <= x; ++ i) {
            if (x % i == 0) {
                flag = 0;
                break;
            }
        }
        if (flag) {
            cout << "Yes\n";
        } else {
            cout << "No\n";
        }
    };
    int tt = 1;
    cin >> tt;
    while (tt--) {
        int x;
        cin >> x;
        check(x);
    }
}




F 小雷的算式

取出所有的数字存在一个数组里,然后再排序按要求输出答案即可

Show Code F

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    string s, str = "";
    cin >> s;
    int len = s.size();
    vector a(len);
    int end = 0;
    for (int i = 0; i < len; ++ i) {
        if (s[i] == '+') {
            a[end] = stoi(str);
            str = "";
            end++;
        } else {
            str += s[i];
        }
    } 
    a[end] = stoi(str);
    sort(a.rbegin(),a.rend());
    int ans = 0;
    for (int i = 0; i <= end; ++ i) {
        cout << a[i] << "+\n"[i == end];
        ans += a[i];
    } 
    cout << ans << "\n";
}




H 聪明且狡猾的恶魔

海盗分金博弈

首先要知道海盗分金决策的三个标准:保命,拿更多的金子,杀人,优先级是递减的。

设s(x) 为n == x的情况需要的金币
设f(x) 为第k个海盗,当前的老大是f(1)
s(1) = x:
显然得到全部金币
s(2) = x:
无论f(1)怎么分配,赞成率一定不低于50%
s(3) = x - 1:
f(2)只要杀了f(1),情况就能退化成s(2)
所以f(2)一定反对,但是要是f(1)死了,f(3)将因为f(2)的贪心而什么都得不到,那么f(1)只需要给f(3)一个金币,就能拉拢f(3)给他投赞成票
s(4) = x - 1:
类似的,f(2)只要杀了f(1),情况就能退化成s(3)
f(1)只需要给f(4)一个金币,就能拉拢f(4)给他投赞成票
s(5) = x - 2:
f(2)只要杀了f(1),情况就能退化成s(4)
f(1)只需要给f(3) f(4)各一个金币,就能拉拢f(3) f(4)给他投赞成票
假设s(m) = x - (m - 1) / 2:
f(1)只需要给f(m - (m - 1) / 2)~f(m)各一个金币,就能拉拢f(m - (m - 1) / 2)~f(m)给他投赞成票
可以推导出s(m + 1) = x - m / 2:
根据数学归纳法,s(n) = x - (n - 1) / 2
(PS:不知道数学归纳法用的对不对,若有差错,请斧正)

也可以看看以下博客,是该题的hard版
https://www.cnblogs.com/dyhaohaoxuexi/p/14427237.html

Show Code H





I 马拉松

由于该国家由 n 个城市组成,编号从 1n ,还有 n1 条双向道路连接着这些城市。从任何一个城市都可以到达另一个城市。

符合无根树的定义(定义1)

无根树有几种等价的形式化定义:
1.有 n 个结点,n1 条边的连通无向图
2.无向无环的连通图
3.任意两个结点之间有且仅有一条简单路径的无向图
4.任何边均为桥的连通图(桥:删去该边即不是连通图的边)
5.没有圈,且在任意不同两点间添加一条边之后所得图含唯一的一个圈的图

当小明经过 x 城市后又跑到 y 城市,那么该城市的警察就会阻止小明继续参加比赛。(从x开始到y也不行)

求举办的所有路线中他会有多少个马拉松会在他参加时会被警察阻止。

根据无根图的定义
x 到 y 之间肯定只有一条路径,该路径上上任意边都是桥
将x y 之间的边删去,分成了两个连通图,两个连通图的大小的乘积即是答案

下面代码的思路就是,先跑一下dij最短路求出x 到 y 之间的路径,再dfs遍历一下x, y开始的无根树,同时求出树的大小再相乘

Show Code I

constexpr ll inf = 0x3f3f3f3f3f3f3f3f;
class edge {
public:
    ll v, w;
};
struct node {
    ll dis, u;
    bool operator>(const node& a) const { return dis > a.dis; }
};
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, a, b;
    cin >> n >> a >> b;
    vector< vector< edge >> adj(n + 1, vector< edge >(0));
    vector< ll > pre(n + 1);
    vector< int > path(n + 1);
    for (int i = 1; i <= n - 1; ++ i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back({v, 1});
        adj[v].push_back({u, 1});
    }
    auto dij = [&](int begin, int end) {
        vector< ll > dis(n + 1 , inf); 
        vector< ll > vis(n + 1);
        priority_queue< node, vector< node >, greater< node > > q;
        dis[begin] = 0;
        q.push({0, begin});
        while (!q.empty()) {
            int u = q.top().u;
            q.pop();
            if (vis[u]) continue;
            vis[u] = 1;
            for (auto ed : adj[u]) {
                int v = ed.v, w = ed.w;
                if (dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    pre[v] = u;
                    q.push({dis[v], v});
                }
            }
        }
        return dis[end];
    };
    auto dfs_path = [&](auto &self, int u) -> void {
        path[u] = 1;
        if (u == a) {
            return ;
        } else {
            self(self, pre[u]);
        }
    };
    dij(a,b);
    dfs_path(dfs_path, b);
    ll na = 0, nb = 0, nn = 1;
    auto dfs = [&](auto self , int x , int p) -> void {
        for (auto ye : adj[x]) {
            int y = ye.v;
            if (y == p || path[y] == 1) continue;
            nn ++;
            self(self , y , x);
        }
    };
    dfs(dfs , a , -1);
    na = nn;
    nn = 1;
    dfs(dfs , b , -1);
    nb = nn;
    ll ans = na * nb;
    cout << ans << "\n";
}




J 尖塔第四强的高手

分析题目可知,本题就是求点集 x+Fi|iN,ik,x+Fin 的最近公共祖先

分析:
所求结点能到达点集中任意一点,即是这些结点的祖先
在所有符合要求的结点中,该结点距离根节点最远。离根结点最远即是离这些点最近,即最近公共祖先

Show Code I

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, r, q;
    cin >> n >> r >> q;
    vector< int > fs(n + 10);
    vector< int > dep(n + 10);
    map< ll, ll >mp;
    vector< vector< int >> adj(n + 10, vector< int >(0));
    vector< vector< int >> fa(n + 10, vector< int >(32));
    fs[0] = 1;
    fs[1] = 1;
    for (int i = 2; i <= n; ++ i) {
        fs[i] = fs[i - 1] + fs[i - 2];
        mp[fs[2]] = i;
    }
    for (int i = 1; i < n; ++ i) {
        int u, v; 
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dep[r] = 0;
    auto dfs = [&](auto self , int x , int p) -> void {
        fa[x][0] = p;
        dep[x] = dep[p] + 1;
        for (int i = 1; i <= 30; ++ i) {
            fa[x][i] = fa[ fa[x][i - 1] ][i - 1];
        }
        for (auto y : adj[x]) {
            if (y == p) continue;
            self(self , y , x);
        }
    };
    dfs(dfs, r, 0);
    auto lca = [&](int x, int y) {
        if (dep[x] > dep[y]) swap(x, y);
        for (int i = 30; i >= 0; -- i) {
            if (dep[fa[y][i]] >= dep[x]) y = fa[y][i];
        } 
        if (y == x) {
            return x;
        }
        for (int i = 30; i >= 0; -- i) {
            if (fa[y][i] != fa[x][i]) {
                y = fa[y][i];
                x = fa[x][i];
            }
        } 
        return fa[y][0];
    };
    while (q--) {
        int x, k;
        cin >> x >> k;
        if (k > n) {
            cout << 0 << "\n";
            continue;
        }
        int ans = 0;
        for (int i = k; x + fs[i] <= n; ++ i) {
            if (i == k) {
                ans = x + fs[i];
            } else {
                ans = lca(ans, x + fs[i]);
            }
        }
        cout << ans << "\n";
    }
}

(PS:菜菜,目前只写了7题的题解)

posted @   Proaes  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示