Educational Codeforces Round 194 div2 个人题解(A~F)

Educational Codeforces Round 194 div2 个人题解(A~F)

https://codeforces.com/contest/2260

A. Monocarp's Contest

题目大意

\(n\) 道题,\(0\) 表示简单题,\(1\) 表示困难题,每次操作可以交换任意两道题,要求最终第一题和最后一题都是简单题,求最少操作次数,无解输出 \(-1\)

解题思路

首先整个数组中至少需要有两个 \(0\),否则一定无解,由于一次操作可以交换任意两个位置,因此只要 \(0\) 的数量不少于两个,就一定可以将两个 \(0\) 放到首尾,此时答案就是首尾两个位置中 \(1\) 的数量:

  • 首尾都是 \(0\),答案为 \(0\)
  • 一个是 \(1\),答案为 \(1\)
  • 两个都是 \(1\),答案为 \(2\)

代码实现

void solve()
{
    int n;
    cin >> n;

    vi a(n);
    int cnt = 0;

    for (auto &x : a)
    {
        cin >> x;
        cnt += x == 0;
    }

    if (cnt < 2)
    {
        cout << -1 << '\n';
        return;
    }

    cout << a[0] + a[n - 1] << '\n';
}

B. Monocarp and Projects

题目大意

\(i\) 个月公司有 \(x+i\) 名员工以及 \(y+i\) 个项目,每名员工必须分到相同数量的项目,因此 Monocarp 自己需要完成 \((y+i)\bmod(x+i)\) 个项目,求前 \(k\) 个月 Monocarp 自己完成的项目总数。

解题思路

直接模拟显然不行,因为 \(k\le 10^{12}\),观察发现 \(y+i=(x+i)+(y-x)\),令 \(d=y-x\),那么有 \((y+i)\bmod(x+i)=d\bmod(x+i)\),于是答案变成 \(\sum_{i=0}^{k-1} d\bmod(x+i)\),当 \(x+i>d\) 时,由于模数已经大于 \(d\),所以 \(d\bmod(x+i)=d\)

因此真正需要枚举的只有满足 \(x+i\le d\) 的部分,枚举长度为 \(\max(0,d-x+1)\),并且 \(d\le y\),题目又保证所有测试数据满足 \(\sum y\le 10^6\),所以直接枚举即可,时间复杂度为 \(O(\sum y)\)

代码实现

void solve()
{
    ll x, y, k;
    cin >> x >> y >> k;

    ll d = y - x;
    ll m = min(k, max(0LL, d - x + 1));

    ll ans = 0;

    for (ll i = 0; i < m; i++)
    {
        ans += d % (x + i);
    }

    ans += (k - m) * d;

    cout << ans << '\n';
}

C. Maximize XOR, Minimize Operations

题目大意

一次操作将 \(x\leftarrow x-1,\ y\leftarrow y+1\)

要求先最大化 \(x\oplus y\),在 XOR 最大的方案中再最小化操作次数。

解题思路

已知 \(\oplus\) 是弱化的加法:不进位加法,设 \(s=x+y\), 则XOR一定满足 \(x'\oplus y'\le s\)

那接下来考虑最少操作次数,要满足 \(x'\oplus y'=x'+y'=s\),必须有 \(x' \& y'=0\) ,也就是说二进制加法中不能出现进位,因为 \(x'+y'=s\),所以 \(x'\) 的所有二进制 \(1\) 都必须是 \(s\) 中已经存在的 \(1\),即 \(x'\) 必须是 \(s\) 的子掩码。

操作次数为 \(x-x'\),因此需要找到不超过 \(x\) 的最大的 \(s\) 的子掩码

从高位向低位贪心,如果当前位在 \(s\) 中为 \(1\),并且加入这一位后仍然不超过 \(x\),那么一定选择这一位,因为 \(2^i>2^i-1\),高位的贡献严格大于所有低位之和。

代码实现

void solve()
{
    ll x, y;
    cin >> x >> y;

    ll s = x + y, z = 0;

    for (int i = 30; i >= 0; i--)
    {
        if ((s >> i & 1) && (z | (1LL << i)) <= x)
        {
            z |= 1LL << i;
        }
    }

    cout << s << ' ' << x - z << '\n';
}

D. Signs of Prefix Sums

题目大意

给定一个只包含 +-0 的字符串 \(s\)

需要构造一个不存在 \(0\) 元素的整数数组 \(a\),令 \(p_i=\sum_{j=1}^{i}a_j\),满足:

  • \(s_i=+\Rightarrow p_i>0\)
  • \(s_i=-\Rightarrow p_i<0\)
  • \(s_i=0\Rightarrow p_i=0\)

定义数组代价为 \(\max |a_i|\),求最小可能代价。

解题思路

把原数组看成前缀和之间的移动,设 \(p_0=0\),并且 \(a_i=p_i-p_{i-1}\) ,因为 \(a_i\neq 0\),所以要求 \(p_i\neq p_{i-1}\)

无解情况:

如果第一位是 0,那么 \(p_1=a_1=0\),显然不合法。

如果出现连续的 00,则 \(p_{i-1}=p_i=0\),所以 \(a_i=0\),同样不合法。

因此 \(s_1=0\) 或者出现 00 时答案为 \(-1\)

其余情况一定有解,并且答案不会超过 \(3\)

判断答案是否为 1:

如果代价为 \(1\),因为 \(a_i\neq0\),所以 \(a_i\in{-1,1}\)

前缀和每次只能移动一,因此两个相邻的非零符号不能直接发生变化,因为从正数移动到负数至少需要移动 \(2\) ,也就是说,每两个 0 之间必须全部是相同符号。

另外考虑一个最后接 0 的同号段,假设长度为 \(L\),路径需要从 \(0\) 出发,经过 \(L\) 个同号前缀和,再回到 \(0\),一共走 \(L+1\) 步,每一步都是 \(\pm1\),要回到 \(0\),步数必须为偶数,因此 \(L+1\equiv0\pmod2\) ,于是 \(L\equiv1\pmod2\)

所以所有以 0 结尾的非零段长度必须是奇数,最后一个没有回到 0 的段没有奇偶限制。

判断答案是否为 3:

接下来假设答案不能是 \(1\),考虑代价能否为 \(2\)

如果相邻符号发生 \(+\rightarrow-\),由于正整数和负整数之间距离至少是 \(2\),在代价限制为 \(2\) 时,只可能 \(1\rightarrow-1\)

反过来同理。

观察字符串:\(+--+\)

左侧的 +- 强制第一个负前缀和为 \(-1\) ,右侧的 -+ 又强制第二个负前缀和为 \(-1\) ,于是出现两个相邻相等的前缀和 \(-1,-1\) ,对应 \(a_i=0\) ,不合法,所以 +--+ 一定需要代价 \(3\) ,完全对称地,-++- 也需要代价 \(3\)

除此之外,所有合法字符串均可以使用代价 \(2\) 构造,因此答案分类为:

  • \(-1\)\(s_1=0\) 或存在 \(00\)
  • \(1\) :满足前文条件
  • \(3\) :存在 \(+--+\)\(-++-\)
  • \(2\) :其他情况

代码实现

void solve()
{
    int n;
    string s;
    cin >> n >> s;
    if (s[0] == '0' || s.find("00") != string::npos)
    {
        cout << -1 << '\n';
        return;
    }
    bool ok = true;
    int lst = -1;
    for (int i = 0; i < n; i++)
    {
        if (i && s[i] != '0' && s[i - 1] != '0' && s[i] != s[i - 1])
        {
            ok = false;
        }

        if (s[i] == '0')
        {
            ok &= (i - lst - 1) & 1;
            lst = i;
        }
    }
    if (ok)
    {
        cout << 1 << '\n';
        return;
    }
    if (s.find("+--+") != string::npos || s.find("-++-") != string::npos)
        cout << 3 << '\n';
    else
        cout << 2 << '\n';
}

E. Cyclic Balance

题目大意

对于一个二进制字符串,将首尾也视为相邻,要求环上四种相邻字符对 \(00,01,10,11\) 出现次数完全相同,对于每次区间询问,求最少需要向对应子串中插入多少个字符,才能使其变成平衡的。

解题思路

设当前子串长度为 \(m\)

记:

  • \(c_0\)\(0\) 的数量;
  • \(c_1\)\(1\) 的数量;
  • \(c\) 为环上的 01 数量。

对于任意一个环形二进制串,一定有 \(01=10\) ,设它们均为 \(c\) ,同时有 \(00=c_0-c\),以及 \(11=c_1-c\) ,假设最终四种相邻对都出现 \(k\) 次,则一定满足 \(00=01=10=11=k\) ,于是最终有 \(c_0=2k\)\(c_1=2k\),并且 \(c=k\),所以最终字符串长度必然为 \(4k\)

原来的字符不能删除,所以必须满足 \(2k\ge c_0\) 以及 \(2k\ge c_1\) ,得到 \(k\ge\left\lceil\frac{c_0}{2}\right\rceil\),以及 \(k\ge\left\lceil\frac{c_1}{2}\right\rceil\)

再考虑环上的 01 数量,在一条相邻边中插入一个字符后,原本的一条边会变成两条边,分类讨论可以发现,01 的数量只可能保持不变或者增加 \(1\),不会减少,因此 \(k\ge c\),最终一共需要插入 \(4k-m\) 个字符。

01 数量需要从 \(c\) 增加到 \(k\),至少需要 \(k-c\) 次能够增加 01 数量的插入,所以 \(4k-m\ge k-c\) ,整理得到 \(3k\ge m-c\),即 \(k\ge\left\lceil\frac{m-c}{3}\right\rceil\)

因此:

\[k= \max\left( \left\lceil\frac{c_0}{2}\right\rceil, \left\lceil\frac{c_1}{2}\right\rceil, c, \left\lceil\frac{m-c}{3}\right\rceil \right). \]

答案就是最终长度减去原长度,即 \(4k-m\)

现在只需要快速求出:

  • 区间内 \(0\) 的数量;
  • 区间内 \(1\) 的数量;
  • 子串环上的 01 数量。

前两个显然可以使用前缀和,对于第三个量,统计环上相邻字符不同的边数 \(d\)

因为环上 \(01=10\),所以 \(c=\frac d2\) ,预处理原字符串中相邻字符是否不同的前缀和,对于询问 \([l,r]\),先统计区间内部相邻字符不同的数量,再加上首尾 \(s_l\neq s_r\) 产生的环形边即可。

代码实现

void solve()
{
    int n, q;
    cin >> n >> q;

    string s;
    cin >> s;

    vi sum(n + 1), pre(n + 1);

    for (int i = 0; i < n; i++)
    {
        sum[i + 1] = sum[i] + (s[i] == '1');
        pre[i + 1] = pre[i] + (i && s[i] != s[i - 1]);
    }

    while (q--)
    {
        int l, r;
        cin >> l >> r;

        int m = r - l + 1;
        int c1 = sum[r] - sum[l - 1];
        int c0 = m - c1;

        int d = pre[r] - pre[l];
        d += s[l - 1] != s[r - 1];

        int c = d / 2;

        int k = max({
            (c0 + 1) / 2,
            (c1 + 1) / 2,
            c,
            (m - c + 2) / 3
        });

        cout << 4 * k - m << '\n';
    }
}

F. Edge Three-Coloring

题目大意

给定一张连通无向图,将每条边染成 \(1,2,3\) 三种颜色之一,要求每种颜色至少出现一次,并满足:

若两个点在颜色 \(i\) 的边构成的图中连通,并且 \(i<j\),那么它们在颜色 \(j\) 中也必须连通,判断是否存在合法染色。

题目给出 \(m\le n+9\)

解题思路

这题主要利用 \(m-n+1\) 很小,首先将原来的染色条件转化一下。

性质一:颜色 3 一定连通整张图

考虑原图中的任意一条边 \((u,v)\) ,如果这条边颜色为 \(3\),那么 \(u,v\) 在颜色 \(3\) 中显然连通;如果颜色为 \(2\),那么 \(u,v\) 在颜色 \(2\) 中连通,根据题意它们在颜色 \(3\) 中也连通;如果颜色为 \(1\),同理最终也能推出 \(u,v\) 在颜色 \(3\) 中连通,因此原图中每一条边的两个端点都属于同一个颜色 \(3\) 连通块,又因为原图本身连通,所以颜色 \(3\) 的边必须连通全部 \(n\) 个点。

性质二:存在一个环,删掉这个环后图仍然连通

因为颜色 \(1\) 至少存在一条边,任选一条颜色 \(1\) 的边 \(e=(u,v)\),由于 \(u,v\) 在颜色 \(1\) 中连通,所以根据题目要求,它们在颜色 \(2\) 中同样连通,因此一定存在一条全由颜色 \(2\) 边构成的 \(u\rightarrow v\) 路径 \(P\) ,路径 \(P\) 加上边 \(e\) 就构成了一个环 \(C\) ,这个环中只有颜色 \(1\) 和颜色 \(2\),没有颜色 \(3\) ,而刚才已经证明颜色 \(3\) 自身就是一个连通生成子图,所以删掉环 \(C\) 后,图仍然连通,因此合法染色一定满足:存在一个环 \(C\),删除 \(C\) 上的全部边之后,原图仍然连通。

那反过来,如果存在这样一个环 \(C\),那么可以:

  • \(C\) 中任选一条边染颜色 \(1\)
  • 环上剩余边全部染颜色 \(2\)
  • 其他所有边染颜色 \(3\)

颜色 \(2\) 构成环去掉一条边后的路径,因此颜色 \(1\) 那条边两端在颜色 \(2\) 中连通,而删掉整个 \(C\) 后图仍然连通,所以颜色 \(3\) 连通整张图,于是三种颜色的要求全部满足。

所以原问题等价于:存在一个环 \(C\),使得 \(G-C\) 连通。

直接枚举图中的所有简单环显然不可行,但是题目给出 \(m\le n+9\) ,连通图的环空间维数为 \(k=m-n+1\),因此 \(k\le10\) ,整个环空间中一共只有 \(2^k\le1024\) 个元素。

考虑任意一棵生成树,树外边一共有 \(m-(n-1)=k\) 条,对于每一条树外边 \((u,v)\),它与生成树上唯一的 \(u\rightarrow v\) 路径构成一个基本环,这 \(k\) 个基本环就是一组环空间基,因此可以枚举这 \(k\) 个基本环的所有异或组合。

某个环空间元素不一定是一个简单环,也可能是若干个环的并,设枚举出的非空边集为 \(S\),并且 \(G-S\) 仍然连通,由于 \(S\) 是非空环空间元素,其中至少包含一个简单环 \(C\) ,又因为 \(C\subseteq S\),那么 \(G-C\) 相比 \(G-S\) 只是多保留了一些边,因此 \(G-C\) 同样连通,所以只需要判断是否存在某个非零环空间元素,删除之后图依然连通即可。

给每一个基本环分配一个二进制位,对于每条边 \(e\),记录一个状态 \(belong_e\),第 \(i\) 位表示这条边是否属于第 \(i\) 个基本环,枚举基本环集合 mask 后,一条边是否出现在异或结果中,只需要判断 \(\operatorname{popcount}(mask\&belong_e)\) 的奇偶性,删除这些出现奇数次的边,然后 BFS 检查剩余图是否连通即可。

时间复杂度为 \(O\left(2^{m-n+1}(n+m)\right)\) ,由于 \(m-n+1\le10\),可以通过。

代码实现

void solve()
{
    int n, m;
    cin >> n >> m;

    vector<pii> e(m);
    vector<vector<pii>> adj(n);

    for (int i = 0; i < m; i++)
    {
        auto &[u, v] = e[i];
        cin >> u >> v;
        --u, --v;

        adj[u].push_back({v, i});
        adj[v].push_back({u, i});
    }

    vi fa(n, -1), dep(n), pe(n, -1);
    vi tree(m), belong(m);

    queue<int> q;
    fa[0] = 0;
    q.push(0);

    while (!q.empty())
    {
        int u = q.front();
        q.pop();

        for (auto [v, id] : adj[u])
        {
            if (fa[v] != -1)
            {
                continue;
            }

            fa[v] = u;
            dep[v] = dep[u] + 1;
            pe[v] = id;
            tree[id] = 1;
            q.push(v);
        }
    }

    int k = m - n + 1;
    int p = 0;

    for (int i = 0; i < m; i++)
    {
        if (tree[i])
        {
            continue;
        }

        int bit = 1 << p++;
        belong[i] |= bit;

        auto [u, v] = e[i];

        while (dep[u] > dep[v])
        {
            belong[pe[u]] |= bit;
            u = fa[u];
        }

        while (dep[v] > dep[u])
        {
            belong[pe[v]] |= bit;
            v = fa[v];
        }

        while (u != v)
        {
            belong[pe[u]] |= bit;
            belong[pe[v]] |= bit;

            u = fa[u];
            v = fa[v];
        }
    }

    for (int mask = 1; mask < (1 << k); mask++)
    {
        vi vis(n);
        queue<int> que;

        vis[0] = 1;
        que.push(0);

        int cnt = 1;

        while (!que.empty())
        {
            int u = que.front();
            que.pop();

            for (auto [v, id] : adj[u])
            {
                if (__builtin_parity(mask & belong[id]) || vis[v])
                {
                    continue;
                }

                vis[v] = 1;
                cnt++;
                que.push(v);
            }
        }

        if (cnt == n)
        {
            cout << "YES\n";
            return;
        }
    }

    cout << "NO\n";
}


总结

这场前六题的核心思路大概可以概括为:

  • A:计数,直接判断首尾;
  • B:利用 \(x+i\)\(y+i\) 的差不变,将 \(10^{12}\) 次枚举压到 \(O(y)\)
  • C:理由 \(\oplus\) 为不进位加法这一性质确定最大和固定,然后将题目转化为最大子掩码;
  • D:将数组转成前缀和路径,分类讨论最小步长 \(1/2/3\)
  • E:从环上四类边数量出发推公式,再使用前缀和 \(O(1)\) 回答询问;
  • F:将染色条件转化为可删除环,利用 \(m-n+1\le10\) 枚举环空间。

赛时第一时间没想到怎么写B,看了一眼C就立马秒了,这个性质在二进制类题目还是常用的。

posted @ 2026-09-09 01:56  ExtractStars  阅读(43)  评论(0)    收藏  举报