Codeforces Round 896 (Div. 2)
D2. Candy Party (Hard Version)
题意:有 个人每人有 个糖果,每个人可以至多给一个人糖果,并且每个人至多可以收到一个人的糖果,但无论是给糖果或者收糖果,糖果的个数都要是 的整数幂,问能否使得所有人的糖果个数相等。
题解:
我们首先假设糖果的平均数是 ,那么和平均值的差值是 ,对于每个 ,我们不难发现:
- 当 的绝对值不是 的整数幂时,只有 一种选择;
- 当 的绝对值是 的整数幂时,有 和 两种选择。
对于每一种情况的前者,我们发现能够构成 的正负两个数是固定的,可以先预处理出来;
对于第二种的情况的后者,我们发现 ,低次幂可以变出一个高次幂;
于是我们可以拿考虑把每一种不可变的组合先算出来存到容器中,即先把 的绝对值不是 的整数幂的先存起来,毕竟它是固定的;
然后把 的绝对值是 的整数幂的值存到另一个容器中;
我们对于幂次从高到低便利,若当前 和 的个数不一样,就从低位中变过来,最后判断能否全部相等。
具体代码如下:
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <iostream>
#include <vector>
#include <cstring>
#include <cmath>
#include <queue>
#include <set>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <bitset>
#include <map>
#include <algorithm>
#include <random>
#define pi acos(-1)
#define all(x) begin(x), end(x)
#define int long long
#define lowbit(m) ((-m)&(m))
#define x first
#define y second
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
const int N = 10000010, M = 510, INF = 1e18, P = 131;
const int mod = 1e9 + 7;
const double esp = 1e-6;
int dx[] = {1, -1, 1, -1}, dy[] = {1, 1, -1, -1};
int Case = 1;
int res = 0, ans = 0;
int n, m;
map<int, int> S;
map<int, PII> Q;// (x : {k + 1, -(q + 1)}) 其中:2^k - 2^q = x
map<int, int> F;// (k + 1 : 2^k )
int f[35];
void init()
{
f[0] = 1;
for (int i = 1; i <= 32; i ++ ) f[i] = f[i - 1] * 2;
for (int i = 0; i <= 32; i ++ )
for (int j = 0; j <= 32; j ++ )
{
S[f[i] - f[j]] = 1;
Q[f[i] - f[j]] = {i + 1, -(j + 1)};
S[-f[i] + f[j]] = 1;
Q[-f[i] + f[j]] = {-(i + 1), (j + 1)};
}
for (int i = 0; i <= 32; i ++ )
{
F[-f[i]] = i + 1;
F[f[i]] = i + 1;
}
}
void solve()
{
cin >> n;
vector<int> a(n + 1, 0);
for (int i = 1; i <= n; i ++ ) cin >> a[i];
int s = 0;
for (auto &x : a) s += x;
if (s % n)
{
cout << "No\n";
return;
}
s /= n;
res = 0;
vector<int> c1(33, 0), c2(33, 0);//第i+1位记录可变正负2^i的个数
vector<int> v1(33, 0), v2(33, 0);//第i+1位记录不可变正负2^i的个数
for (int i = 1; i <= n; i ++ )
{
int d = s - a[i];
if (!d) continue;
if (!S[d])
{
cout << "No\n";
return;
}
if (F[d])
{
int k = F[d];
d > 0 ? c1[k] ++ : c2[k] ++ ;
}
else
{
int t1 = Q[d].first, t2 = Q[d].second;
t1 > 0 ? v1[abs(t1)] ++ : v2[abs(t1)] ++ ;
t2 > 0 ? v1[abs(t2)] ++ : v2[abs(t2)] ++ ;
}
}
for (int i = 32; i > 1; i -- )
{
if (c1[i] + v1[i] == c2[i] + v2[i]) continue;
if (c1[i] + v1[i] > c2[i] + v2[i])// +多
{
int d = (c1[i] + v1[i]) - (c2[i] + v2[i]);
if (c2[i - 1] < d)
{
cout << "No\n";
return;
}
c2[i - 1] -= d;
v1[i - 1] += d;
}
else
{
int d = (c2[i] + v2[i]) - (c1[i] + v1[i]);
if (c1[i - 1] < d)
{
cout << "No\n";
return;
}
c1[i - 1] -= d;
v2[i - 1] += d;
}
}
if (c1[1] + v1[1] == c2[1] + v2[1]) cout << "Yes\n";
else cout << "No\n";
}
signed main()
{
init();
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int _ = 1;
cin >> _;
while (_ -- ) solve();
return 0;
}
E. Travel Plan
题意:有 个节点编号从 到 ,其中第 号节点与 号节点和 号节点相连,每个节点都有一个范围为 的值,定义 表示从第 号节点到第 号节点简单路径上的值的最大值,求 。
题解:读题发现 的范围超级大,于是思考别的计数方法。答案是由不同长度的路径上的最大值累加而成,所以我们不妨先对答案按路径长度分类,再将每一种长度的路径对答案的贡献相加即可得到答案。(这里长度的定义是一条路径上包含的点的数目)
先给出柿子,我们假设 是路径的长度, 是路径上值的最大值, 是该路径长度在连通图中出现的次数,那么此时答案是:
;
于是我们最后需要的答案就是:
。
解释一下第一个式子:
一条长为 ,路径上最大值为 的路径会使得答案增加 。
那么如何计算有多少种长为 ,路径上最大值为 的路径?
对于在路径上的节点,我们要保证值的最大值是 ,我们可以先算出 ,表示每个节点的值任意选 之间的值的方案数,但是我们要保证存在 ,于是可以减去 ,表示每个节点的值任意选 之间的值的方案数,那么剩下的方案数里面一定存在至少一个节点的值是 。
那么对于剩下的 个节点我们就可以任意选 之间的值,即 。
于是我们得到所有长为 ,路径上最大值为 的路径会对答案产生的贡献,就是:
。
除了 之外,我们都可以在开始时预处理出来;
对于 ,我们可以枚举 LCA ,
我们可以枚举每个点,然后计算出其两个子树内深度为 的点各有多少个,然后两重循环就可以统计出以这个点为 LCA
的长度为 的路径数量。
值的注意的是,每条路径要么是先往上再往下,要么是直接往下,于是考虑枚举其左右子树的层数(定义一棵子树的根处在第 0 层,则如果此时正在枚举 作为 LCA
,其左儿子所处子树的第 层有 个结点,其右儿子所处子树的第 层有 个结点,于是产生了 条长度(结点数)为 的路径,因为根处在第 0 层, 即是 LCA+左儿子+右儿子
)。
但是点太多了,不可能枚举所有点,我们可以把点分层,可以发现每层内的子树状态最多只有三种(最后一层是满的,最后一层不满但有点,最后一层没有点),可以二分找到分界点,这样一层只需要计算三次即可。
具体代码如下:
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <iostream>
#include <array>
#include <vector>
#include <cstring>
#include <cmath>
#include <queue>
#include <set>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <bitset>
#include <map>
#include <algorithm>
#include <random>
#include <iomanip>
#define CASE cout << "Case #" << Case ++ << ": "
#define pi acos(-1)
#define all(x) begin(x), end(x)
#define int long long
#define lowbit(m) ((-m)&(m))
#define x first
#define y second
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
const int N = 100010, M = 125, INF = 1e18, P = 131;
const int mod = 998244353;
const double esp = 1e-6;
int dx[] = {1, -1, 1, -1}, dy[] = {1, 1, -1, -1};
int Case = 1;
int res = 0, ans = 0;
int n, m;
LL qmi(LL a, LL b, int p)
{
LL res = 1;
while (b)
{
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
int pw[N][M], inv[N][M]; // i^j and i^(-j)
int cnt[M];
void init()
{
for (int j = 0; j < M; j ++ ) pw[1][j] = inv[1][j] = 1;
for (int i = 2; i < N; i ++ )
{
pw[i][0] = 1;
for (int j = 1; j < M; j ++ ) pw[i][j] = pw[i][j - 1] * i % mod;
inv[i][M - 1] = qmi(pw[i][M - 1], mod - 2, mod);
for (int j = M - 2; j >= 0; j -- ) inv[i][j] = inv[i][j + 1] * i % mod;
}
}
void solve()
{
cin >> n >> m;
for (int i = 0; i < M; i ++ ) cnt[i] = 0;
auto get = [&](int x)
{
vector<int> c(61, 0);
if (x > n) return c;
c[0] = 1;
if (x * 2 > n) return c;
int level = 1;
int l = x << 1;
do{
int r = min(n, l + (1ll << level) - 1);
c[level] = (r - l + 1) % mod;
l <<= 1, level ++ ;
}while (l <= n);
return c;
};
int level = 0;
while ((1ll << level) <= n)
{
int len = (1ll << level);
int l = len, r = (len << 1) - 1, ans = (len << 1);
auto sz = get(l);
while (l <= r)
{
int mid = l + r >> 1;
if (get(mid) != sz) r = mid - 1, ans = mid;
else l = mid + 1;
}
auto cal = [&](int u, int c)
{
c %= mod;
auto root = get(u), L = get(u << 1), R = get(u << 1 | 1);
//同一子树向下拓展
for (int i = 1; i + level < 61; i ++ ) (cnt[i] += root[i - 1] * c) %= mod;
//左右子树之间的跨越
for (int i = 0; i + level < 61; i ++ )
for (int j = 0; j + level < 61; j ++ )
(cnt[i + j + 3] += L[i] * R[j] % mod * c) %= mod;
};
l = len;
cal(l, ans - l);
if (ans < (len << 1)) cal(ans, 1);
if (ans + 1 < (len << 1)) cal(ans + 1, (len << 1) - (ans + 1));
level ++ ;
}
res = 0;
int pw_mn = qmi(m, n % (mod - 1), mod);
for (int len = 1; len <= min(n + 1, M); len ++ )
for (int mx = 1; mx <= m; mx ++ )
{
int t1 = cnt[len] * mx % mod;
int t2 = (pw[mx][len] - pw[mx - 1][len] + mod) % mod;
int t3 = pw_mn * inv[m][len] % mod;// m^n * m^(-len) = m^(n-len)
(res += t1 * t2 % mod * t3) %= mod;
}
cout << res << endl;
}
signed main()
{
init();
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int _ = 1;
cin >> _;
while (_ -- ) solve();
return 0;
}