目录
目录
树
给一棵根为 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