2022牛客冬令营 第二场 题解

A题 小沙的炉石(数学,思维)

现在我们有 \(n\) 张攻击牌和 \(m\) 张回蓝牌。攻击牌消耗一格蓝量,并且对对方造成 1 点普通伤害和附带的法伤。回蓝牌则回复一格蓝量(蓝量无上限)。

此外,我们每使用一张牌之后,可以使得我们的法力伤害值增加 1,可叠加(初始值为 0)。

现在有 \(k\) 次询问,每次给定敌方血量 \(x\),问我们能否找到一个出牌顺序,使得地方能够恰好被我们斩杀(不要求用完牌)。

\(1\leq n\leq 10^9,0\leq m \leq 10^9,1\leq k\leq 10^5\),看不懂法伤那部分可以看样例。

我们不难找到伤害最大化的方法并计算出来:前面疯狂堆回复牌,然后后面能打多少进攻牌就打多少,值的话就直接等差数列套个公式就行。

接下来,我们看看我们可以构造出哪些攻击方式。

在进行 \(t\) 次攻击的情况下,我们至少可以造成 \(t^2\) 的伤害(1010101这种方式),至多造成 \(\frac{t(2m+t+1)}{2}\) 的伤害。值得注意的是,这是连续的(临项交换法,将攻击牌往后面交换位置)。

不过显然,我们不能每次一个个求出来区间,然后每次询问的时候都去遍历一遍,那么我们只能看看这些区间能不能合并(显然是可以的)

实践发现,仅存在 \(t=2,m=1,x=3\)\(t=3,m=2,b=8\) 两种情况是特例(可以自己草稿纸上面推,也可以写个暴力直接莽),其余情况下直接看在不在 \([1,Max]\) 里面即可。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
int main()
{
    LL n, m, k;
    cin >> n >> m >> k;
    LL t = min(n, m + 1), Max = t * (2 * m + t + 1) / 2;
    while (k--) {
        LL x;
        cin >> x;
        if ((t == 2 && m == 1 && x == 3) || (t == 3 && m == 2 && x == 8))
            puts("NO");
        else
            puts(x > Max ? "NO" : "YES");
    }
    return 0;
}

B题 小沙的魔法(排序,离散化,图上遍历)

给定一个 \(n\) 个点的图,每个点有一个权值 \(a_i\)。现在我们每次可以进行一次操作,使得某个点的权值下降 1,我们的目标是使得所有点的权值变为 0。

不过,单纯这样操作有点慢,所以我们还有 \(m\) 个连接器,每个连接器可以将两个点进行连接,之后每次操作可以视为对整个连通块进行一次操作。连接器至多使用 \(\min(5n,m)\) 次。

\(1\leq n \leq 5*10^5,1\leq m \leq \min(\frac{n(n-1)}{2},5*10^6),0\leq a_i\leq 10^9\)

(至多 \(n-1\) 次连接就可以使得整个图联通,所以连接器的次数限制无意义。)

这题其实和著名的 P1969 [NOIP2013 提高组] 积木大赛 很类似,不过那题是线性,本题搬到了图上面。

先排个序,然后离散化一下,从大到小依次处理。我勉强看得懂题解代码和思路,但有一说一,自己真写不出来。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 500010;

int n, m, s[N];
vector<int> e[N], ans[N], mp;
int F(int x) { return lower_bound(mp.begin(), mp.end(), x) - mp.begin(); }
//
int fa[N];
int find(int x) {
    if (x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}
bool merge(int x, int y) {
    x = find(x), y = find(y);
    if (x != y) fa[x] = y;
    return x != y;
}
int main()
{
    scanf("%d%d", &n, &m);
    mp.resize(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &s[i]);
        mp[i] = s[i];
    }
    //离散化
    sort(mp.begin(), mp.end());
    mp.erase(unique(mp.begin(), mp.end()), mp.end());
    for (int i = 1; i <= n; i++)
        ans[F(s[i])].push_back(i);
    //图的构建
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        e[u].push_back(v), e[v].push_back(u);
    }
    //solve
    for (int i = 1; i <= n; ++i) fa[i] = i;
    LL res = 0, cnt = 0;
    for (int i = mp.size() - 1; i >= 1; i--) {
        for (int x : ans[i]) {
            cnt++;
            for (int y : e[x])
                if (s[y] >= s[x])
                    if (merge(x, y)) cnt--;
        }
        res += (mp[i] - mp[i - 1]) * cnt;
    }
    //output
    printf("%lld", res);
    return 0;
}

C题 小沙的杀球(贪心)

小沙特别喜欢杀球(羽毛球的一个玩法),但是水平有限,只能杀后场的高远球,杀不了前场的小球。

小沙是有体力限制的,每杀一个球就会消耗 \(a\) 体力,但是不杀球的话则会回复 \(b\) 体力。体力没有上限,但是体力不可能为负。

现在小沙有体力 \(x\),现在即将有 \(n\) 个球打过来,并且知道他们分别是哪种球(按顺序),试求出小沙究竟最多能杀多少球?

\(0\leq x,a,b\leq 10^9,1\leq n\leq 10^6\)

按照贪心,尽可能的来杀高远球,如果体力不够或者是小球就休息一下,恢复体力。

这题会卡 int,所以记得开 long long。

#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n;
char s[N];
int main()
{
    long long x, a, b;
    scanf("%lld%lld%lld%s", &x, &a, &b, s + 1);
    n = strlen(s + 1);
    int ans = 0;
    for (int i = 1; i <= n; ++i)
        if (s[i] == '1' && x >= a)x -= a, ans++;
        else x += b;
    printf("%d", ans);
    return 0;
}

D题 小沙的涂色(unsolved)

  • 小思维

  • 细节

  • 中模拟

  • 构造

  • 有许多种构造思路,这里列举出一种

  • 如果边长是3的倍数我们便无法构造出合法方案。因为我们需要填色的格子数一定是3的倍数,所以要满足 \((n^2-1)=0 \mod 3\)

    对于边长mod 3情况下是1的情况,我们可以选择试边界向内缩小6格的方案使他一直维持 mod 3 为 1,最后只剩一个格子停下即可,对于边长 mod 3 为 2 的情况,我们需要让他变成 1,所以我们可以选择向内缩小 4格的方案,从外向内染色。

  • 也可以使用二分的方法染色,解法很多,只要你觉得好写~

  • 时间复杂度\(O(n^2)\)

E题 小沙的长路(图论推理)

给定一个 \(n\) 个点的完全图,现在我们可以任意给这些边加方向,问加上之后这个图的最长路的可能的最小值和最大值。

\(1\leq n \leq 10^9\)

  • 最大值

    \(n\) 为奇数的时候,直接把图当成一个无向图来走欧拉通路即可(每个点的度都是 \(n-1\),偶数,所以存在欧拉通路),然后按照路径来标边的方向,值为 \(\frac{n(n-1)}{2}\)

    \(n\) 为偶数的时候,所有点的度数都是奇数,不存在欧拉通路。如果我们将最长路之外的边删掉,意味着这条最长路就是剩下的边所组成的图的欧拉通路。那显然,删掉 \(\frac{n}{2}-1\) 条边的时候,可以将 \(n-2\) 个点的度数减去一(变成偶数),再留下两个奇数点,满足构成欧拉回路的条件,所以答案为 \(\frac{n(n-1)}{2}-(\frac{n}{2}-1)\)(也是删边最少的方案)

  • 最小值

    我们逐个添加点,发现每添加一个点,最长路径至少增加 1(可以根据每个点的入度出度来判断)。

    由数学归纳法可知,最小值为 \(n-1\)

#include<bits/stdc++.h>
using namespace std;
int main()
{
    long long n;
    cin >> n;
    printf("%lld %lld", n - 1, n * (n - 1) / 2 - (n % 2 ? 0 : n / 2 - 1));
    return 0;
}

F题 小沙的算数 (模拟,逆元)

现在有一个长度为 \(n\),仅含加号和乘号的表达式,每个位置上面有一个初始值。

现在,有 \(q\) 次询问,每次询问会要求将位置 \(x\) 上面的数改成 \(y\),然后计算一下表达式的值并且输出。

\(n\leq 10^6,q\leq 10^5\),答案对 \(10^9+7\) 取模且题目中给出的所有数值都在 \([1,10^9+7)\) 范围内。

仅包含加号和乘号,那我们可以将不同位置的数,打上不同的标记(相同标记表示在一个连通块内),一个连通块的值等于等于该标记的值的乘积,表达式的值等于所有连通块之和。

对于每次询问,我们找到位置 \(x\) 对应的连通块,然后就是一连串的先除再乘,顺便更新答案即可。因为取模的原因,所以还要写下逆元啥的。

#include<bits/stdc++.h>
using namespace std;
//
#define LL long long
const int N = 1000010;
const LL mod = 1000000007;
LL power(LL a, LL b) {
    a %= mod;
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}
LL inv(LL x) {
    return power(x, mod - 2);
}
//
int n, q;
char opt[N];
int vis[N];
LL a[N], v[N];
int main()
{
    scanf("%d%d", &n, &q);
    scanf("%s", opt + 1);
    //
    vis[1] = 1;
    for (int i = 1; i < n; ++i)
        vis[i + 1] = vis[i] + (opt[i] == '+');
    //
    for (int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    for (int i = 1; i <= vis[n]; ++i) v[i] = 1;
    for (int i = 1; i <= n; ++i)
        v[vis[i]] = v[vis[i]] * a[i] % mod;
    //
    LL ans = 0;
    for (int i = 1; i <= vis[n]; ++i)
        ans += v[i];
    while (q--) {
        int x;
        LL y;
        scanf("%d%lld", &x, &y);
        int tag = vis[x];
        ans = (ans - v[tag] + mod) % mod;
        v[tag] = v[tag] * inv(a[x]) % mod * y % mod;
        ans = (ans + v[tag]) % mod;
        a[x] = y;
        printf("%lld\n", ans);
    }
    return 0;
}

G题 小沙的身法(LCA)

给定一个有 \(n\) 个点的联通树,每个点有一个高度 \(a_i\)

我们可以沿着树上面的边移动,从 \(x\) 跳到 \(y\),需要花费 \(\max(a_y-a_x,0)\) 的体力(从高处往低处跳不需要体力,反之则需要耗费高度差的体力)。

现在有 \(m\) 次询问,每次询问给定起点和终点,我们从地上跳到起点,然后一路跳到终点后跳回地上,问需要耗费的体力是多少?(地面可以视为高度为 0,但是只有开头和结束可以跳)

\(1\leq n \leq 10^6,1\leq m\leq 10^5,1\leq a_i\leq 10^9\)

树上路径,多次查询,那显然就是倍增 LCA了。

考虑到具有方向性,所以我们需要构建两颗树,均以 1 为根,然后一个按照从深度低往深度高的方式赋边权,另一棵树反过来,然后每次询问的时候分别查询即可。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1000010;
int lg[N];
//
int n, m, a[N];
//
vector<int> tree[N];
int depth[N], fa[N][23];
LL dis1[N], dis2[N];
void dfs(int x, int f) {
	depth[x] = depth[f] + 1;
	fa[x][0] = f;
	for (int i = 1; (1 << i) <= depth[x]; i++)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for (int y : tree[x])
		if (y != f) {
            dis1[y] = dis1[x] + max(a[x] - a[y], 0);
            dis2[y] = dis2[x] + max(a[y] - a[x], 0);
            dfs(y, x);
        }
}
int lca(int x, int y) {
	if (depth[x] < depth[y]) swap(x, y);
	while (depth[x] > depth[y])
		x = fa[x][lg[depth[x] - depth[y]]];
	if (x == y) return x;
	for (int k = lg[depth[x]]; k >= 0; k--)
		if (fa[x][k] != fa[y][k])
			x = fa[x][k], y = fa[y][k];
	return fa[x][0];
}
int main()
{
    lg[1] = 0;
	for (int i = 2; i < N; i++)
		lg[i] = lg[i / 2] + 1;
	//read & build
	scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    dfs(1, 0);
    while (m--) {
        int u, v;
        scanf("%d%d", &u, &v);
        int x = lca(u, v);
        printf("%lld\n", a[u] + (dis1[u] - dis1[x]) + (dis2[v] - dis2[x]));
    }
    return 0;
}

H题 小沙的数数(数学)

已知一个长度为 \(n\) 的非负整数数列 \({a_n}\),数值和为 \(m\)

现在我们想要让数列的异或和最大,尝试找出有几种方案。

\(1\leq n \leq 10^{18},0\leq a_i,m\leq 10^{18}\),方案数对 \(10^9+7\) 取模。

不是很好证明,异或和的最大值就是 \(m\):感性上讲,就是讲 \(m\) 的二进制上面的 1,被均匀分配到 \(n\) 个坐标的不同位置上面,能够使得异或和最大。而任何尝试使异或值变得更大的方式,都会使得数值和超过 \(m\)

分配方式很奇妙:我们记 \(m\) 在二进制下有 \(t\) 个 1,那么每个位置的 1 都有 \(n\) 个选择,所以答案是 \(n^t\)

\(n\) 的规模比较离谱,所以一开始就得取个模,不然直接炸 long long。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod = 1e9 + 7;

int main()
{
    LL n, m, ans = 1;
    cin >> n >> m;
    n %= mod;
    for (; m; m >>= 1)
        if (m % 2) ans = ans * n % mod;
    cout << ans;
    return 0;
}

I题 小沙的构造(模拟,构造)

直接看代码,别问咋构造的,我 WA 了 12 发才过就离谱。

#include<bits/stdc++.h>
#include<bits/stdc++.h>
using namespace std;
string str1 = "\"!'*+-.08:=^_WTYUIOAHXVM|";//0 1 2 ... 24
string str2 = "<>\\/[]{}()";// 01 23 45 67 89
int n, m;
string solve() {
    deque<char> q1, q2, q3;
    int cnt = 0, len = 0;
    for (int i = 0; i < 5 && cnt + 2 < m; ++i) {
        q1.push_front(str2[2 * i]);
        q3.push_back (str2[2 * i + 1]);
        cnt += 2, len += 2;
    }
    for (int i = 0; i < 24 && cnt + 1 < m; ++i) {
        q1.push_front(str1[i]);
        q3.push_back (str1[i]);
        cnt += 1, len += 2;
    }
    if (n - len <= 0) return "-1";
    for (int i = 0; i < n - len; ++i)
        q2.push_back(str1[24]);

    string res = "";
    for (char c : q1) res.push_back(c);
    for (char c : q2) res.push_back(c);
    for (char c : q3) res.push_back(c);
    return res;
}
int main()
{
    //read
    cin >> n >> m;
    //solve
    string ans;
    if (m == 36 || (m > 11 && n < 2 * m - 11) || (m <= 11 && n < m)) ans = "-1";
    else ans = solve();
    //solve
    cout << ans;
    return 0;
}

J题 小沙的Dota(unsolved)

  • DDP(所需知识点线段树)

  • 考虑转移方程 \(dp_{i,v}=\min(dp_{i,v},dp_{i-1,x})\)

  • 在需要修改的情况下,我们可以采用线段树维护带修改的 DP 过程。

  • 对于节点维护左状态为 V 的情况下变化到 X 所需要的最小代价,预处理各个状态之间的代价转移即可

  • 时间复杂度:\(O(6^3(n+m)\log n)\)

  • \(O(6^4(n+m)\log n)\)的合并写丑了可能会被卡。

K题 小沙的步伐 (签到)

除了在 5 上面不用动,别的地方直接都是目标位置和 5 都加 1。

#include<bits/stdc++.h>
using namespace std;
int ans[10];
int main()
{
    string str;
    cin >> str;
    for (char c : str)
        if (c != '5') ans[c - '0']++, ans[5]++;
    for (int i = 1; i < 10; ++i)
        printf("%d ", ans[i]);
    return 0;
}

L/M题 小沙的remake(DP,树状数组,排序)

不难写出一个 \(O(n^2)\) 的 DP:

for (int i = 1; i <= n; ++i) {
    dp[i] = 1;
    for (int j = max(i - b[i], 1); j < i; ++j)
        if (a[i] >= a[j])
            dp[i] = (dp[i] + dp[j]) % mod;
}
//output
LL ans = 0;
for (int i = 1; i <= n; ++i)
    ans = (ans + dp[i]) % mod;
printf("%lld", ans);

这题是最长上升子序列的一种变形(从最长变成了求方案数,同时加上了范围限制),以前是可以通过树状数组+离散化来优化复杂度,但是对这题似乎不太够。

这题比较特殊,求方案数,所以我们在传统下标上面建立树状数组(用来求和),但是遍历DP的时候按照排序过后的下标来,这样恰好满足了:

  1. 可以限定下标范围(显然的,就相当于上面那个朴素DP的优化)
  2. 根据按照数值排序后的下标顺序来进行状态转移,保证了每次查询之后的值都是合法的,可以直接求和
#include <bits/stdc++.h>
#define LL long long
namespace GenHelper
{
    int z1, z2, z3, z4, z5, u, res;
    int get()
    {
        z5 = ((z1 << 6) ^ z1) >> 13;
        z1 = ((int)(z1 & 4294967) << 18) ^ z5;
        z5 = ((z2 << 2) ^ z2) >> 27;
        z2 = ((z2 & 4294968) << 2) ^ z5;
        z5 = ((z3 << 13) ^ z3) >> 21;
        z3 = ((z3 & 4294967) << 7) ^ z5;
        z5 = ((z4 << 3) ^ z4) >> 12;
        z4 = ((z4 & 4294967) << 13) ^ z5;
        return (z1 ^ z2 ^ z3 ^ z4);
    }
    int read(int m)
    {
        u = get();
        u >>= 1;
        if (m == 0)
            res = u;
        else
            res = (u / 2345 + 1000054321) % m;
        return res;
    }
    void srand(int x)
    {
        z1 = x;
        z2 = (~x) ^ (0x23333333);
        z3 = x ^ (0x12345798);
        z4 = (~x) + 51;
        u = 0;
    }
}
using namespace GenHelper;
using namespace std;
const int N = 2e6 + 7, mod = 1e9 + 7;
int n, seed;
int a[N], b[N];
pair<int, int> p[N];
//TreeArray
#define lowbit(x) (x & (-x))
LL t[N];
LL query(int x) {
    LL res = 0;
    for (; x; x -= lowbit(x))
        res = (res + t[x]) % mod;
    return res;
}
void add(int x, int val) {
    for (; x <= n; x += lowbit(x))
        t[x] = (t[x] + val) % mod;
}
//Main
int main()
{
    scanf("%d %d", &n, &seed);
    srand(seed);
    for (int i = 1; i <= n; i++) {
        a[i] = read(0), b[i] = read(i);
        p[i] = {a[i], i};
    }
    sort(p + 1, p + n + 1);
    LL ans = 0;
    for (int i = 1; i <= n; i++) {
        int id = p[i].second;
        LL val = (query(id) - query(max(0, id - b[id] - 1)) + 1 + mod) % mod;
        add(id, val);
        ans = (ans + val) % mod;
    }
    cout << ans << endl;
    return 0;
}
posted @ 2022-02-03 20:00  cyhforlight  阅读(43)  评论(0编辑  收藏  举报