Codeforces补题7.0

Codeforces Round 896 (Div. 2)

D2. Candy Party (Hard Version)

题意:有 n 个人每人有 a_i 个糖果,每个人可以至多给一个人糖果,并且每个人至多可以收到一个人的糖果,但无论是给糖果或者收糖果,糖果的个数都要是 2 的整数幂,问能否使得所有人的糖果个数相等。

题解:

我们首先假设糖果的平均数是 s ,那么和平均值的差值是 d=s-a_i ,对于每个 d ,我们不难发现:

  1. 当 d 的绝对值不是 2 的整数幂时,只有 (\pm 2^k,\mp 2^{k-1}) 一种选择;
  2. 当 d 的绝对值是 2 的整数幂时,有 (\pm 2^k,\mp 2^{k-1}) 和 (\pm 2^k,\mp 0) 两种选择。

对于每一种情况的前者,我们发现能够构成 d 的正负两个数是固定的,可以先预处理出来;

对于第二种的情况的后者,我们发现 2^k = 2^{k+1}-2^k ,低次幂可以变出一个高次幂;

于是我们可以拿考虑把每一种不可变的组合先算出来存到容器中,即先把 d 的绝对值不是 2 的整数幂的先存起来,毕竟它是固定的;

然后把 d 的绝对值是 2 的整数幂的值存到另一个容器中;

我们对于幂次从高到低便利,若当前 +2^k 和 -2^k 的个数不一样,就从低位中变过来,最后判断能否全部相等。

具体代码如下:

#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

题意:有 n 个节点编号从 1 到 n ,其中第 i 号节点与 2i 号节点和 2i+1 号节点相连,每个节点都有一个范围为 [1,m] 的值,定义 s_{i,j} 表示从第 i 号节点到第 j 号节点简单路径上的值的最大值,求 \sum_{i=1}^{n}\sum_{j=1}^{n} s_{i,j} 。

题解:读题发现 n 的范围超级大,于是思考别的计数方法。答案是由不同长度的路径上的最大值累加而成,所以我们不妨先对答案按路径长度分类,再将每一种长度的路径对答案的贡献相加即可得到答案。(这里长度的定义是一条路径上包含的点的数目

先给出柿子,我们假设 len 是路径的长度, mx 是路径上值的最大值, cnt[len] 是该路径长度在连通图中出现的次数,那么此时答案是:

cnt[len] \times \left [ mx^{len}-(mx-1)^{len} \right ] \times m^{n-len} \times mx ;

于是我们最后需要的答案就是:

\sum_{len=1}^{min(n,2logN))} \sum_{mx=1}^{m} cnt[len] \times \left [ mx^{len}-(mx-1)^{len} \right ] \times m^{n-len} \times mx 。

解释一下第一个式子:

一条长为 len ,路径上最大值为 mx 的路径会使得答案增加 cnt[len] \times mx 。

那么如何计算有多少种长为 len ,路径上最大值为 mx 的路径?

对于在路径上的节点,我们要保证值的最大值是 mx ,我们可以先算出 mx^{len} ,表示每个节点的值任意选 \left [ 1,mx \right ] 之间的值的方案数,但是我们要保证存在 mx ,于是可以减去 (mx-1)^{len} ,表示每个节点的值任意选 \left [ 1,mx-1 \right ] 之间的值的方案数,那么剩下的方案数里面一定存在至少一个节点的值是 mx 。

那么对于剩下的 n-len 个节点我们就可以任意选 \left [ 1,m \right ] 之间的值,即 m^{n -len} 。

于是我们得到所有长为 len ,路径上最大值为 mx 的路径会对答案产生的贡献,就是:

cnt[len] \times \left [ mx^{len}-(mx-1)^{len} \right ] \times m^{n-len} \times mx

除了 cnt[len] 之外,我们都可以在开始时预处理出来;

对于 cnt[len] ,我们可以枚举 LCA ,我们可以枚举每个点,然后计算出其两个子树内深度为 i 的点各有多少个,然后两重循环就可以统计出以这个点为 LCA 的长度为 i 的路径数量。

值的注意的是,每条路径要么是先往上再往下,要么是直接往下,于是考虑枚举其左右子树的层数(定义一棵子树的根处在第 0 层,则如果此时正在枚举 u 作为 LCA ,其左儿子所处子树的第 i 层有 L[i] 个结点,其右儿子所处子树的第 j 层有 R[j] 个结点,于是产生了 L[i] \times R[j] 条长度(结点数)为 i+j+3 的路径,因为根处在第 0 层,+3 即是 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值