「2018泉州国庆集训#1」 – 解题报告

A – 礼物

sb 暴力出奇迹,欺骗人的感情。

敢情您特么\(1e9\)的复杂度都能过?真的服气。

// A.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1010;

int prefix[MAX_N][MAX_N], suffix[MAX_N][MAX_N];
int n, ai[MAX_N], bi[MAX_N], ci[MAX_N], m;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d%d", &ai[i], &bi[i], &ci[i]);
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < MAX_N; j++)
        {
            prefix[i][j] = prefix[i - 1][j];
            if (j >= ai[i])
                prefix[i][j] = max(prefix[i][j], prefix[i - 1][j - ai[i]] + bi[i]);
        }
    for (int i = n; i >= 1; i--)
        for (int j = 0; j < MAX_N; j++)
        {
            suffix[i][j] = suffix[i + 1][j];
            if (j >= ai[i])
                suffix[i][j] = max(suffix[i][j], suffix[i + 1][j - ai[i]] + bi[i]);
        }
    scanf("%d", &m);
    while (m--)
    {
        int del, money;
        scanf("%d%d", &del, &money), del++;
        int ans = 0;
        for (int i = 0; i <= money; i++)
            ans = max(ans, prefix[del - 1][i] + suffix[del + 1][money - i]);
        printf("%d\n", ans);
    }
    return 0;
}

B – 异或

这道题太特么傻逼了。

考虑一个位有可能作为答案的贡献,当且仅当之前的位都相等。在 Trie 树上,任意一个节点都可以保证前缀相等,所以每一个节点的贡献就是\(2^{len} \times son_0 \times son_1\)。

// B.cpp
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int MAX_N = 1e5 + 200;
const ll mod = 199907210507;

struct node
{
    int ch[2];
    ll val;
} nodes[MAX_N * 70];

ll ptot = 1, n, arr[MAX_N], ans, pow2[70];

void insert(ll x)
{
    int p = 1;
    for (int i = 0; i <= 62; i++)
    {
        int bit = (x >> i) & 1;
        if (nodes[p].ch[bit] == 0)
            nodes[p].ch[bit] = ++ptot;
        nodes[p].val++, p = nodes[p].ch[bit];
    }
}

void dfs(int u, int cnt)
{
    if (u == 0)
        return;
    (ans += 1LL * nodes[nodes[u].ch[0]].val * nodes[nodes[u].ch[1]].val % mod * pow2[cnt]) %= mod;
    dfs(nodes[u].ch[0], cnt + 1), dfs(nodes[u].ch[1], cnt + 1);
}

int main()
{
    scanf("%lld", &n);
    for (int i = pow2[0] = 1; i < 70; i++)
        pow2[i] = 1LL * pow2[i - 1] * 2 % mod;
    for (int i = 1; i <= n; i++)
        scanf("%lld", &arr[i]), insert(arr[i]);
    dfs(1, 0), printf("%lld", 1LL * ans * 2 % mod);
    return 0;
}

C – 子集

非常奇妙的一道题。

答案是\(2^{n \times k}\)。我想了很久的原因来解释这个答案,也感谢 XG_Zepto 和 CrazyDave 的思考。我们可以发现,选择一种单调(非偏序的)的点集合,并往这样的集合里面填入数字即可确定整个方案。

我们指「非偏序的点」,形如图中两个 B 点的关系:

A
^
|
A <- A
^    ^
|    |
A <- A <- B
^    ^    ^
|    |    |
A <- B <- A <- A

定义这样的点为某个元素最早存在的点,往这样的点中放元素可以让上游的区域全部覆盖该元素。考虑有\(2^k\)个这样的点集合(也就是「非偏序的点」组成的集合的子集个数),然后每个元素放入某个集合,那么元素个数就是\(2^{nk}\)。

大概这样,我还需要再进行思考。

关于「非偏序的点」组成的集合的子集个数为\(2^k\)的证明 from XG_Zepto:

记\(f(k)\)为从第\(k\)行开始向上的非偏序点集合个数,考虑第\(k\)行包括了之前的所有情况,所以\(f(k) = 2 + \sum_{i = 1}^{k – 1} f(i)\),然后这个\(2\)代表着独占情况。

大概就是这样。

// C.cpp
#include <bits/stdc++.h>
#define ll unsigned long long

using namespace std;

const ll mod = 7528443412579576937;

ll quick_mul(ll x, ll y)
{
    ll res = 0;
    if (x < y)
        swap(x, y);
    while (y)
    {
        if (y & 1LL)
            res = (res + x < mod) ? (res + x) : (res + x - mod);
        x = (x + x < mod) ? (x + x) : (x + x - mod);
        y >>= 1LL;
    }
    return res;
}

ll quick_pow(ll bas, ll tim)
{
    ll ans = 1;
    while (tim)
    {
        if (tim & 1LL)
            ans = quick_mul(ans, bas);
        bas = quick_mul(bas, bas);
        tim >>= 1LL;
    }
    return ans;
}

int main()
{
    ll n, k;
    scanf("%lld%lld", &n, &k), printf("%lld", quick_pow(2, 1LL * n * k));
    return 0;
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *