Codeforces Pinely Round 4 (Div. 1 + Div. 2) A~G

A.Maximize the Last Element(枚举)

题意:

给你一个由 n n n个整数组成的数组 a a a,其中 n n n奇数

在一次操作中,你将从数组 a a a中删除两个相邻的元素,然后将数组的剩余部分连接起来。例如,在数组 [ 4 , 7 , 4 , 2 , 9 ] [4,7,4,2,9] [4,7,4,2,9]中,我们可以通过操作 [ 4 , 7 ‾ , 4 , 2 , 9 ] → [ 4 , 2 , 9 ] [\underline{4,7},4,2,9]\to[4,2,9] [4,7,4,2,9][4,2,9] [ 4 , 7 , 4 , 2 ‾ , 9 ] → [ 4 , 7 , 9 ] [4,7,\underline{4,2},9]\to[4,7,9] [4,7,4,2,9][4,7,9]分别得到数组 [ 4 , 2 , 9 ] [4,2,9] [4,2,9] [ 4 , 7 , 9 ] [4,7,9] [4,7,9]。然而,我们无法得到数组 [ 7 , 2 , 9 ] [7,2,9] [7,2,9],因为需要删除不相邻的元素 [ 4 ‾ , 7 , 4 ‾ , 2 , 9 ] [\underline{4},7,\underline{4},2,9] [4,7,4,2,9]

我们将重复执行这一操作,直到 a a a中只剩下一个元素。

a a a中剩余元素的可能的最大值。

分析:

遍历数组,对于每个位置检查两侧数的数量是否为奇数,若为奇数则只有该位置可以保留。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 1005;
int a[N];

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        for (int i = 1; i <= n; ++i)
            cin >> a[i];
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            if ((i - 1) % 2 == 1 || (n - i) % 2 == 1)
                continue;
            ans = max(ans, a[i]);
        }
        cout << ans << endl;
    }
    return 0;
}

B.AND Reconstruction(枚举)

题意:

给你一个由 n − 1 n-1 n1个整数组成的数组 b b b

如果 b i = a i   &   a i + 1 b_i=a_i\,\&\,a_{i+1} bi=ai&ai+1 1 ≤ i ≤ n − 1 1\le i\le n-1 1in1,其中 & \& &表示按位与,那么由 n n n个整数组成的数组 a a a称为好数组。

构造一个好数组,或输出不存在好数组。

分析:

对于每一位分别考虑,若 b i b_i bi该位为 1 1 1,则 a i a_i ai a i + 1 a_{i+1} ai+1的这一位都必须是 1 1 1;默认其余的都为 0 0 0,最后检验是否合法即可。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 100005;
int n, a[N], b[N], c[N];

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i < n; ++i)
            cin >> b[i];
        for (int i = 1; i <= n; ++i) {
            a[i] = 0;
        }
        int flag = 1;
        for (int j = 0; j < 30 && flag; ++j) {
            for (int i = 1; i <= n; ++i)
                c[i] = 0;
            for (int i = 1; i < n; ++i)
                if ((b[i] >> j) & 1)
                    c[i] = c[i + 1] = 1;
            for (int i = 1; i < n; ++i) {
                if (!((b[i] >> j) & 1) && c[i] && c[i + 1]) {
                    flag = 0;
                    break;
                }
            }
            for (int i = 1; i <= n; ++i) {
                if (c[i])
                    a[i] |= (1 << j);
            }
        }
        if (!flag) {
            cout << "-1" << endl;
            continue;
        }
        for (int i = 1; i <= n; ++i)
            cout << a[i] << " ";
        cout << endl;
    }
    return 0;
}

C.Absolute Zero(暴力)

题意:

给你一个由 n n n个整数组成的数组 a a a

在一次操作中,将执行以下两步移动:

  1. 选择一个整数 x x x( 0 ≤ x ≤ 1 0 9 0\le x\le 10^{9} 0x109)。
  2. 将每个 a i a_i ai替换为 ∣ a i − x ∣ |a_i-x| aix,其中 ∣ v ∣ |v| v表示 v v v的绝对值。

例如,选择 x = 8 x=8 x=8后,数组 [ 5 , 7 , 10 ] [5,7,10] [5,7,10]将变为 [ ∣ 5 − 8 ∣ , ∣ 7 − 8 ∣ , ∣ 10 − 8 ∣ ] = [ 3 , 1 , 2 ] [|5-8|,|7-8|,|10-8|]=[3,1,2] [∣58∣,∣78∣,∣108∣]=[3,1,2]

构建一个操作序列,使 a a a中的所有元素在最多 40 40 40次操作中等于 0 0 0,或者确定这是不可能的。需要尽量减少运算次数。

分析:

最多操作 40 40 40次,考虑暴力。

设当前最大值为 m a x x maxx maxx,最小值为 m i n n minn minn。每次减去 ( m a x x − m i n n ) / 2 + m i n n (maxx-minn)/2+minn (maxxminn)/2+minn,这样操作之后,整体区间上界必然减半。可以计算出如果有解,操作次数一定足够。

代码:

#include<bits/stdc++.h>

using namespace std;
int a[200005];

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    int q = 40;
    vector<int> ans;
    while (q--) {
        int maxx = -1;
        int minn = 1e9 + 1;
        for (int i = 1; i <= n; i++) {
            maxx = max(maxx, a[i]);
            minn = min(minn, a[i]);
        }
        int tmp = max(1, (maxx - minn) / 2 + minn);
        if (maxx - minn == 0) {
            ans.push_back(maxx);
            for (int i = 1; i <= n; i++) {
                a[i] = 0;
            }
            break;
        }
        for (int i = 1; i <= n; i++) {
            a[i] = abs(a[i] - tmp);
        }
        ans.push_back(tmp);
    }
    bool flag = true;
    for (int i = 1; i <= n; i++) {
        if (a[i]) {
            flag = false;
            break;
        }
    }
    if (flag) {
        cout << ans.size() << endl;
        for (auto x: ans) {
            cout << x << ' ';
        }
        cout << endl;
    } else {
        cout << "-1" << endl;
    }
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

D.Prime XOR Coloring(思维)

题意:

给你一个无向图,图中有 n n n个顶点,编号从 1 1 1 n n n。当且仅当 u ⊕ v u\oplus v uv是质数时,顶点 u u u v v v之间有一条边,其中 ⊕ \oplus 表示按位异或。

用最少的颜色给图中的所有顶点着色,使得由一条边直接连接的两个顶点没有相同的颜色。

分析:

我们注意到如果两个整数之差为 4 4 4的倍数,那么他们异或也一定是 4 4 4的倍数,显然是一个合数。

由此可以推出四种颜色的万能方案。

观察样例发现 n = 6 n=6 n=6时需要四种颜色,对更小的数据参考样例进行特判即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;

int main() {
    int T;
    cin >> T;
    while (T--) {
        LL n;
        cin >> n;
        if (n <= 5) {
            cout << n / 2 + 1 << endl;
            for (int i = 1; i <= n; ++i)
                cout << i / 2 + 1 << " ";
            cout << endl;
        } else {
            cout << 4 << endl;
            for (int i = 1; i <= n; ++i)
                cout << (i % 4) + 1 << " ";
            cout << endl;
        }
    }
    return 0;
}

E.Coloring Game(构造)

题意:

这是一个互动问题。

考虑一个由 n n n个顶点和 m m m条边组成的无向连接图。每个顶点可以用三种颜色中的一种着色: 1 1 1 2 2 2 3 3 3。初始时,所有顶点都未着色。

爱丽丝和鲍勃正在玩一个包含 n n n轮的游戏。在每一轮中,都会发生以下两个步骤:

  1. 爱丽丝选择两种不同的颜色。
  2. 鲍勃选择一个未着色的顶点,并用爱丽丝选择的两种颜色之一为其着色。

如果存在连接两个相同颜色顶点的边,则爱丽丝获胜。否则,鲍勃获胜。

给你这个图。你的任务是决定你想扮演哪位玩家并赢得游戏。

分析:

读题猜测与二分图染色有关。原图要么是二分图要么有奇环,可以用染色法判断。

那么对于上述情况分类讨论:

  • 原图存在奇环。那么肯定选爱丽丝,一直输出12即可,由于有奇环,所以鲍勃必输。

  • 原图为二分图。首先选鲍勃。然后按照二分图把点分成两个集合, 1 1 1染左边, 2 2 2染右边,一个集合染没了另一个集合染另外 2 2 2种颜色即可,由于只有 3 3 3种颜色,所以一定会有 1 1 1个能染的颜色。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 1e4 + 10;

vector<int> e[N];
int col[N], flag, l[N], r[N];

void dfs(int u, int c) {
    col[u] = c;
    for (int v: e[u]) {
        if (!col[v])
            dfs(v, 3 - c);
        else if (col[v] != 3 - c) {
            flag = 0;
            return;
        }
    }
}

int n, m, u, v, cntl, cntr;

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) {
            col[i] = 0;
            e[i].clear();
        }
        for (int i = 1; i <= m; i++) {
            cin >> u >> v;
            e[u].push_back(v);
            e[v].push_back(u);
        }
        flag = 1;
        dfs(1, 1);
        if (flag == 0) {
            cout << "Alice" << endl;
            cout.flush();
            for (int i = 1; i <= n; i++) {
                cout << "1 2" << endl;
                cout.flush();
                cin >> u >> v;
            }
        } else {
            cout << "Bob" << endl;
            cout.flush();
            cntl = cntr = 0;
            for (int i = 1; i <= n; i++) {
                if (col[i] == 1)
                    l[++cntl] = i;
                else
                    r[++cntr] = i;
            }
            for (int i = 1; i <= n; i++) {
                int a, b;
                cin >> a >> b;
                if (a > b)
                    swap(a, b);
                if (a == 1) {
                    if (cntl)
                        cout << l[cntl--] << " " << a << endl;
                    else
                        cout << r[cntr--] << " " << b << endl;
                } else {
                    if (cntr)
                        cout << r[cntr--] << " " << b << endl;
                    else
                        cout << l[cntl--] << " " << a << endl;
                }
                cout.flush();
            }
        }
    }
    return 0;
}

F.Triangle Formation(思维)

题意:

给你 n n n根木棒,编号从 1 1 1 n n n。第 i i i根木棒的长度是 a i a_i ai

你需要回答 q q q个问题。在每个查询中,你都会得到两个整数 l l l r r r( 1 ≤ l < r ≤ n 1\le l\lt r\le n 1l<rn, r − l + 1 ≥ 6 r-l+1\ge 6 rl+16)。请判断是否可能从编号为 l l l r r r的小棒中选择 6 6 6不同的小棒组成 2 2 2非退化三角形 ∗ ^{\text{∗}}

∗ ^{\text{∗}} 如果边长为 a a a b b b c c c的三角形叫做非退化三角形,那么:

  • a < b + c a\lt b+c a<b+c
  • b < a + c b\lt a+c b<a+c
  • c < a + b c\lt a+b c<a+b

分析:

可以发现,先将区间排序一遍不会改变答案,以下默认查询的区间是不增的。

考虑存在解的充要条件是存在两个不相交的三元组 ( x , y , z ) (x,y,z) (x,y,z) ( x < y < z ) (x\lt y\lt z) (x<y<z)使得 a x < a y + a z a_x\lt a_y+a_z ax<ay+az

注意到若每次取出最大的三个数 x , y , z x,y,z x,y,z,当 x ≥ y + z x≥y+z xy+z时,即不能构成三元组时,则有 x ≥ 2 z x≥2z x2z,此时最大值减半。又当最大值为 1 1 1时若数有至少 6 6 6个,显然必能构成至少两个三元组。考虑若无法找到满足条件的三元组,最大值会在 l o g n logn logn次后递减至 1 1 1。所以当查询的区间长度大于 k k k时,必然有解。其中 k k k O ( l o g n ) O(logn) O(logn)的。

考虑当 l e n < k len\lt k len<k时如何求解。设当前第 i i i大为 c i c_i ci,注意到当 c 1 ≥ c 2 + c 3 c_1≥c_2+c_3 c1c2+c3 c 1 c_1 c1是无用的,直接删去即可。

否则考虑前 6 6 6大值,枚举每种情况看能否组成两个三元组。若不能,则让 c 1 , c 2 , c 3 c_1,c_2,c_3 c1,c2,c3直接组成一个三元组并删去即可。

该贪心正确的证明如下:若两个三元组的最大值均在前 6 6 6大中,则将后面的数替换成前 6 6 6大是不劣的。否则利用前 3 3 3大构成一个三元组也是不劣的。

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 100005;
int n, m, a[N], p, l, r, c[15];

priority_queue<int> s;

bool check(int x, int y, int z) {
    int xx = 0, yy = 0, zz = 0;
    for (int i = 2; i <= 6; i++)
        if (i != x && i != y && i != z) {
            if (!xx)
                xx = i;
            else if (!yy)
                yy = i;
            else
                zz = i;
        }
    return (c[x] < c[y] + c[z] && c[xx] < c[yy] + c[zz]);
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    while (m--) {
        while (!s.empty())
            s.pop();
        cin >> l >> r;
        if (r - l + 1 > 80) {
            cout << "YES" << endl;
            continue;
        }
        for (int i = l; i <= r; i++)
            s.emplace(a[i]);
        p = 2;
        while (p && (int) s.size() >= p * 3) {
            if (p == 1) {
                for (int i = 1; i <= 3; i++)
                    c[i] = s.top(), s.pop();
                if (c[1] < c[2] + c[3]) {
                    p = 0;
                    break;
                }
                s.emplace(c[2]), s.emplace(c[3]);
                continue;
            }
            for (int i = 1; i <= 6; i++)
                c[i] = s.top(), s.pop();
            for (int i = 2; i <= 6; i++)
                for (int j = i + 1; j <= 6; j++)
                    if (check(1, i, j)) {
                        p = 0;
                        break;
                    }
            if (!p)
                break;
            else if (c[1] < c[2] + c[3]) {
                p--;
                for (int i = 4; i <= 6; i++)
                    s.emplace(c[i]);
            } else
                for (int i = 2; i <= 6; i++)
                    s.emplace(c[i]);
        }
        cout << (p ? "NO" : "YES");
        cout << endl;
    }
    return 0;
}

G.Grid Reset(构造)

题意:

给你一个由 n n n行和 m m m列组成的网格,其中每个单元格最初都是白色的。此外,你还会得到一个整数 k k k,其中 1 ≤ k ≤ min ⁡ ( n , m ) 1\le k\le\min(n,m) 1kmin(n,m)

你将处理两种类型的 q q q操作:

  • H \mathtt{H} H(水平操作)-在网格中选择一个完全位于网格内的 1 × k 1\times k 1×k矩形,该矩形中的所有单元格都是白色的。然后,该矩形中的所有单元格都变为黑色。
  • V \mathtt{V} V(垂直操作)-在网格中选择一个 k × 1 k\times 1 k×1矩形,该矩形中的所有单元格都是白色。然后,该矩形中的所有单元格都将变为黑色。

每次操作后,如果有任何行或列变为全黑,这些行或列中的所有单元格都会同时重置为白色。具体来说,如果某个单元格所在行和列的所有单元格都变为黑色,则该行和该列中的所有单元格都将重置为白色。

选择矩形时,应确保可以执行所有给定的操作,或者确定这是不可能的。

分析:

以下是确保可以无限执行操作的策略:

  • 只对最左边的 k k k列进行水平操作,只对最上面的 k k k行进行垂直操作。
  • 优先执行会导致重置的操作。如果多个操作会导致重置,则执行其中任何一个操作;如果没有操作会导致重置,则执行任何可用操作。

对于一个 n × m n×m n×m网格,如果它的黑色单元格可以被不重叠的 1 × m 1×m 1×m矩形无间隙地覆盖,我们就定义它处于水平状态。同样,如果网格中的黑色单元格可以被不重叠的 n × 1 n×1 n×1矩形无间隙地覆盖,则网格处于垂直状态。

我们的策略可以确保网格始终满足以下属性:

  • 左上角的 k × k k×k k×k区域、左下角的 ( n − k ) × k (n−k)×k (nk)×k区域和右上角的 k × ( m − k ) k×(m−k) k×(mk)区域要么处于水平状态,要么处于垂直状态,要么两者都处于水平状态。
  • 左上角和左下角区域不能都纯粹处于垂直状态;同样,左上角和右上角区域也不能都纯粹处于水平状态。

可以证明当满足这些属性时,总有一个有效的操作;根据我们的策略执行操作后,网格仍然满足这些属性。

有效操作证明:

假设当前操作是水平操作(垂直操作的证明类似)。如果左上角和左下角区域无法执行水平操作,那么这两个区域必须是全黑或纯垂直状态。如果其中一个区域是全黑的,那么另一个区域就不可能是全黑或纯垂直状态,因为那样会导致重置。根据第二个属性,左上角和左下角区域不能都处于纯垂直状态。因此,总有一个操作是有效的。

保持第一个属性证明:

对于左下角和右上角区域,它们始终满足第一属性。证明考虑左下角区域(右上角区域的证明类似)。它开始时是全白的,然后逐行变黑,保持水平状态。一旦完全变黑,它就会逐列重置,保持垂直状态。因此,它在水平和垂直状态之间交替。

对于左上角区域,它始终满足第一个属性。证明在着色和重置之前,左上角区域仍然满足第一个属性。假设它在重置前处于水平状态(垂直状态的证明类似)。由于它不能垂直重置,因此重置后至少仍处于水平状态。如果它完全变黑并水平重置(垂直重置的证明类似),它仍处于水平状态。除非 k = 1 k=1 k=1,否则行与列不能同时复位。假设行和列都可以重置,并假设当前操作在左上角区域是水平的(垂直操作的证明类似)。这意味着左上角区域在完全变黑之前处于纯水平状态。右上角区域的一行是完全黑色的,而其他行是白色的,这与操作前的第二个属性相矛盾。

保持第二属性证明:

假设当前操作是水平操作(垂直操作的证明类似)并导致重置,则第二属性仍然成立。证明如果操作位于左下角区域,则重置前该区域为全黑。重置后,左上角区域变为全白,保持第二个性质。我们之前证明过,除非 k = 1 k=1 k=1,否则行和列不能同时重置。如果水平操作在左上角区域,且列重置,则左上角区域在重置前是完全黑色的。重置后,左上角区域变为垂直,而左下角区域变为白色,保持第二个属性。如果在左上角区域进行了水平操作,且行重置,则左上角区域保持不变,而右上角区域的行变为白色。如果右上角区域的状态发生变化并重置为纯水平状态(唯一可能违反第二属性的情况),则右上角区域在重置前是完全黑色的。因此,左上角区域在重置前并不处于纯水平状态。由于左上角区域保持不变,因此它不可能处于纯水平状态,从而保持了第二个属性。

假设当前操作是水平操作(垂直操作的证明与此类似),并且不会导致重置,则第二个性质仍然成立。证明如果操作在左下角区域,则保持水平状态,维持第二个属性。如果操作在左上角区域,唯一可能违反第二属性的情况是右上角区域保持纯水平状态,而左上角区域变成纯水平状态。这意味着左上角区域在操作前是完全白色的。在这种情况下,我们可以选择重置右上角区域中任何完全黑色的行。根据策略,我们会优先重置,从而导致矛盾。因此,第二个性质仍然成立。

代码:

#include<bits/stdc++.h>

using namespace std;
const int MAX_SIZE = 105;
char operationType;
int n, m, k, q, grid[MAX_SIZE][MAX_SIZE];
string s;

int calculateSum(int x1, int y1, int x2, int y2) {
    int sum = 0;
    for (int i = x1; i <= x2; i++)
        for (int j = y1; j <= y2; j++)
            sum += grid[i][j];
    return sum;
}

void performOperation(int x, int y) {
    cout << x << ' ' << y << '\n';
    for (int i = 1; i <= k; i++) {
        grid[x][y] = 1;
        if (operationType == 'H')
            y++;
        else
            x++;
    }
    int rowSums[MAX_SIZE] = {}, colSums[MAX_SIZE] = {};
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            rowSums[i] += grid[i][j];
            colSums[j] += grid[i][j];
        }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (rowSums[i] == m || colSums[j] == n)
                grid[i][j] = 0;
}

void solve() {
    cin >> n >> m >> k >> q >> s;
    s = ' ' + s;
    memset(grid, 0, sizeof(grid));
    for (int i = 1; i <= q; i++) {
        operationType = s[i];
        if (operationType == 'H') {
            int row = -1;
            for (int j = 1; j <= n; j++)
                if (calculateSum(j, 1, j, k) == 0) {
                    row = j;
                    if (calculateSum(j, 1, j, m) == m - k) {
                        break;
                    }
                }
            performOperation(row, 1);
        } else {
            int col = -1;
            for (int j = 1; j <= m; j++)
                if (calculateSum(1, j, k, j) == 0) {
                    col = j;
                    if (calculateSum(1, j, n, j) == n - k) {
                        break;
                    }
                }
            performOperation(1, col);
        }
    }
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值