2024 杭电多校第一场

目录

目录

博弈

传送


给一棵根为 1 的有根树,点 i 具有一个权值 Ai 。

定义一个点对的值 f(u,v)=max(Au,Av)×|Au−Av| 。

你需要对于每个节点 i ,计算 ansi=∑u∈subtree(i),v∈subtree(i)f(u,v) ,其中 subtree(i) 表示 i 的子树。

请你输出 ⊕(ansi mod 2^64) ,其中 ⊕ 表示 XOR。

满足 n≤5×105,1≤Ai≤106

用到线段树合并+权值线段树

max(Au,Av)*|Au-Av| = max(Au,Av) * max(Au,Av) - max(Au,Av) * min(Au,Av)

=

考虑用线段树维护三个值:区间和、区间平方和、区间个数

右区间一定大于左区间,当我们算两个子树合并的贡献时,用右区间的区间平方数乘上左区间的区间个数可以得到 (u,v都在i子树内,且跨越i相连), 左右区间和相乘可以得到

如果v在u子树里,v子树的答案已经算过了,在算u时直接加上v的答案即可。

关于线段树合并+权值线段树的复杂度:

是权值线段树,总点数和n的规模相差并不大。并且合并时一般不会重复地合并某个线段树,所以我们最终增加的点数大致是nlogn级别的。这样,总的复杂度就是nlogn级别的。(摘自oiwiki)

另外注意的小点:

⊕(ansi mod 2^64) 直接用unsigned long long 自然溢出即可,mod会爆

献上调了两小时的代码一份:

cpp 复制代码
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N = 2e6 + 10;
int a[N];
vector<int> to[N];
int dp[N], rt[N << 3], ls[N << 3], rs[N << 3], sz[N << 3];
int sum[N << 3], sum2[N << 3];

void update(int id) {
    sum2[id] = sum2[ls[id]] + sum2[rs[id]];
    sum[id] = sum[ls[id]] + sum[rs[id]];
    sz[id] = sz[ls[id]] + sz[rs[id]];
}
int now ;
int merge(int a, int b, int x, int y) {
    if (!a)return b;
    if (!b)return a;
    if (x == y) {
        sz[a] += sz[b];
        sum2[a] += sum2[b];
        sum[a] += sum[b];
        return a;
    }
    int mid = (x + y) >> 1;
    if (ls[a] != 0 && rs[b] != 0) {
       now += sz[ls[a]] * sum2[rs[b]] * 2ll,now -= sum[ls[a]] * sum[rs[b]] * 2ll;
    }
    if (ls[b] != 0 && rs[a] != 0) {
        now+= sz[ls[b]] * sum2[rs[a]] * 2ll, now -= sum[ls[b]] * sum[rs[a]] * 2ll;
    }
    ls[a] = merge(ls[a], ls[b], x, mid);
    rs[a] = merge(rs[a], rs[b], mid + 1, y);
    update(a);
    return a;
}

int cnt = 0;

int add(int &id, int x, int y, int co) {
    if (!id) id = ++cnt;
    if (x == y) {
        sum2[id] += co * co;
        sum[id] += co;
        sz[id]++;
        return id;
    }
    int mid = (x + y) >> 1;
    if (co <= mid) {
        ls[id] = add(ls[id], x, mid, co);
    } else {
        rs[id] = add(rs[id], mid + 1, y, co);
    }
    update(id);
    return id;
}

int ans = 0;
void dfs(int x, int f) {
    int res = 0;
    add(rt[x], 1, 1000000, a[x]);
    for (int i = 0; i < to[x].size(); i++) {
        int v = to[x][i];
        if (v == f)continue;
        dfs(v, x);
        res += dp[v];
        now = 0;
        merge(rt[x], rt[v], 1, 1000000);
        res+=now;
    }
    dp[x] = res;
    ans ^= res;
    return;
}

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n - 1; i++) {
        int u, v;
        cin >> u >> v;
        to[u].push_back(v);
        to[v].push_back(u);
    }
    for (int i = 1; i <= n; i++)cin >> a[i];
    dfs(1, -1);
    cout << ans << '\n';
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int _ = 1;
    while (_--) solve();
    return 0;
}

博弈

小马给出了一个可重小写字符集合 S。

Alice 初始时有空串 A,Bob 初始时有空串 B。

两人轮流等概率取出集合 S 中的一个字符 c,将它拼接到自己的字符串的后面,直至 S 为空,每个字符只能被取一次,Alice 先手。

如果最终 A 的字典序严格大于 B,则 Alice 胜利,求其获胜的概率,答案对 998244353取模。

考虑 为偶数的情况:

i)平局,所有字符都有偶数个:

A先手随便拿一个字符ai,B后手跟着拿ai,A继续拿aj,B跟着拿aj,那么平局的概率p就是

ii)非平局的情况,无非胜或败,败的情况翻转一下就是胜,所以胜局的概率就是

sum为奇数的情况:

i)讨论前sum-1个可以达到平局的情况,在这个情况下由于A先手,所以A必胜

此时所有字符有且只有一个奇数a[idx],我们先将这个奇数拿出一个,然后重复上面的平局过程:

易得

我们可以简化计算,当拿出了奇数中的一个后,a[idx]--,sum--。(具体看代码就懂了)

ii)前sum-1个非平局的情况,无非胜或败,败的情况翻转一下就是胜,此时胜局的概率就是

iii)总的来说,此时胜局的概率就是

cpp 复制代码
#include<bits/stdc++.h>

#define int long long
using namespace std;
typedef long long ll;
const int N = 1e7 + 10;
const int mod = 998244353;

int a[30], n, f[N];

ll poww(ll a, ll b) {

    ll t = 1;
    while (b) {
        if (b & 1)t = t * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return t;

}

void init() {

    f[1] = 1;
    for (int i = 3; i <= N - 5; i += 2) {
        f[i] = f[i - 2] * i % mod;
    }

}

void mul(int &a, int b) {
    a = ((a % mod * b % mod) + mod) % mod;
}

void solve() {
    cin >> n;
    int sum = 0, cnt = 0, idx = -1;
    char c;
    for (int i = 1; i <= n; i++) {
        cin >> c >> a[i], sum += a[i];
        if (a[i] & 1) cnt++, idx = i;
    }

    int p = 1;

    if (sum & 1) {

        if (cnt != 1) p = 0;
        else {
            mul(p, a[idx] * poww(sum, mod - 2) % mod);
            sum--;
            mul(p, poww(f[sum - 1], mod - 2));
            for (int i = 1; i <= n; i++) {
                if (i == idx) {
                    if (a[i] > 2)mul(p, f[a[i] - 2]);
                } else {
                    mul(p, f[a[i] - 1]);
                }
            }
        }

        cout << ((1 + p) % mod * poww(2, mod - 2)) % mod << '\n';

    } else {

        if (cnt != 0)p = 0;
        else {
            mul(p, poww(f[sum - 1], mod - 2));
            for (int i = 1; i <= n; i++) {
                mul(p, f[a[i] - 1]);
            }
        }

        cout << ((1 - p + mod) % mod * poww(2, mod - 2) % mod + mod) % mod;

    }

}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int _ = 1;
    init();
    cin >> _;
    while (_--) solve();
    return 0;
}
/*
1
2
a 2
c 2
332748118
 */

传送

线段树分治+可撤销并查集

学了之后发现是板子题,贴个链接https://blog.csdn.net/landexiangmz/article/details/140587889?spm=1001.2014.3001.5502

相关推荐
დ旧言~11 分钟前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
张彦峰ZYF15 分钟前
投资策略规划最优决策分析
分布式·算法·金融
The_Ticker31 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
Dola_Pan1 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
烦躁的大鼻嘎2 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
C++忠实粉丝2 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
用户37791362947552 小时前
【循环神经网络】只会Python,也能让AI写出周杰伦风格的歌词
人工智能·算法
福大大架构师每日一题2 小时前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言