AtCoder ABC 366题解

前言

本文部分思路来自于网络,仅供参考。

A - Election 2

题目大意

给定两个市长候选人的票数,求结果是否已经确定。

解题思路

如果剩下的人全部投票给票少的人票少的人也不能赢,则结果就已经确定了。

code

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n,t,a;
    scanf("%d%d%d",&n,&t,&a);
    if(n - t - a + min(t,a) < max(t,a)) {
        printf("Yes\n");
    } else {
        printf("No\n");
    }
    return 0;
}

B - Vertical Writing

题目大意

给定 \(n\) 段英文,将这 \(n\) 段英文从右往左,从上往下输出。

解题思路

定义一个数组 \(C[i][j]\) 为第 \(n - j + 1\) 段英文的第 \(i\) 个字符。只需要把每一段英文填入 \(C\) 数组,然后输出即可。

code

#include <bits/stdc++.h>
using namespace std;
string s[105];
char c[105][105];
int si[105];
int main() {
    int n,m = 0;
    cin >> n;
    for(int i = 1;i <= n;i++) {
        cin >> s[i];
        m = max((int)s[i].size(),m);
    }
    for(int i = 1;i <= m;i++) {
        for(int j = 1;j <= n;j++) {
            if(i > s[j].size()) {
                c[i][n - j + 1] = '*';
                continue;
            }
            c[i][n - j + 1] = s[j][i - 1];
            si[i] = max(si[i],n - j + 1);
        }
    }
    for (int i = 1; i <= m; i++) {
        for(int j = 1;j <= si[i];j++) {
            printf("%c",c[i][j]);
        }
        puts("");
    }
    return 0;
}

C - Balls and Bag Query

题目大意

有三种操作:

  • 1 x 在包里放入一个写了数字 \(x\) 的球。
  • 2 x 从包里拿出一个写了数字 \(x\) 的球。
  • 3 输出包里有几种球。

给定一个操作序列,按顺序执行操作。

解题思路

维护一个变量 \(cnt\) ,表示包里有几种球。如果有一种球被拿完了,则 \(cnt\) 减一;如果新拿进来一种球,则 \(cnt\) 加一。每一次 3 操作输出 \(cnt\) 即可。

code

#include <bits/stdc++.h>
using namespace std;
int cnt = 0;
int arr[1000005];
void add(int x) {
    arr[x]++;
    cnt += (arr[x] == 1); 
}
void del(int x) {
    arr[x]--;
    cnt -= (arr[x] == 0);
}
int main() {
    int q;
    scanf("%d",&q);
    int op,x;
    for(int i = 1;i <= q;i++) {
        scanf("%d",&op);
        if(op == 1) {
            scanf("%d",&x);
            add(x);
        } else if (op == 2) {
            scanf("%d", &x);
            del(x);
        } else {
            printf("%d\n",cnt);
        }
    }
    return 0;
}

D - Cuboid Sum Query

题目大意

给定一个三维数组 \(A\) ,对于每一组输入进来的 \((Lx_i,Rx_i,Ly_i,Ry_i,Lz_i,Rz_i)\) ,输出:

\[\sum\limits_{x=Lx_i}^{Rx_i}\sum\limits_{y=Ly_i}^{Ry_i}\sum\limits_{z=Lz_i}^{Rz_i} A[x][y][z] \]

解题思路

直接用三位前缀和优化即可。

code

#include <bits/stdc++.h>
using namespace std;
int a[105][105][105];
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k <= n; k++) {
                scanf("%d", &a[i][j][k]);
                a[i][j][k] +=
                    a[i - 1][j][k] + a[i][j - 1][k] +
                    a[i][j][k - 1] - a[i - 1][j - 1][k] -
                    a[i - 1][j][k - 1] -
                    a[i][j - 1][k - 1] +
                    a[i - 1][j - 1][k - 1];
            }
        }
    }
    int q, lx, rx, ly, ry, lz, rz;
    scanf("%d", &q);
    while (q--) {
        scanf("%d%d%d%d%d%d", &lx, &rx, &ly, &ry, &lz, &rz);
        printf("%d\n",
               a[rx][ry][rz] - a[lx - 1][ry][rz] -
                   a[rx][ly - 1][rz] - a[rx][ry][lz - 1] +
                   a[lx - 1][ly - 1][rz] +
                   a[lx - 1][ry][lz - 1] +
                   a[rx][ly - 1][lz - 1] -
                   a[lx - 1][ly - 1][lz - 1]);
    }
    return 0;
}

E - Manhattan Multifocal Ellipse

题目大意

给定 \(N\) 个点 \((x_i,y_i)\)\(D\) ,求有多少个点 \((x,y)\) 使得 \(\sum_{i = 1}^{N}\left|x - x_i\right| + \left|y-y_i\right| \leq D\)

解题思路

显然,\(\sum_{i = 1}^{N}\left|x - x_i\right| + \left|y-y_i\right| = \sum_{i = 1}^{N}\left|x - x_i\right| + \sum_{i = 1}^{N}\left|y - y_i\right|\) ,所以可以把 \(x\)\(y\) 分开算。

维护两个数组 \(sumx\)\(sumy\)\(sumx[i]\) 表示 \(\sum_{j = 1}^{N}\left|i - x_j\right|\)\(sumy[i]\) 表示 \(\sum_{j = 1}^{N}\left|i - y_j\right|\) 。其中,\(sumx[i]\) 的值可以拆解为 \(\sum_{x_j < i}\left(x_j - i\right) + \sum_{x_j \geq i}\left(i - x_j\right)\)。计算这两项的时间复杂度为 \(\textrm{O}(n)\) 。为了快速求出这两项,可以维护前缀和 \(presum\) 、后缀和 \(sufsum\) 和小于 \(i\)\(x_j\) 个数 \(it\),则 \(sumx[i]\) 可以转化为 \(it \cdot i - presum + sufsum - (n - it) \cdot i\) ,时间复杂度为 \(\textrm{O}(1)\) 。同理,\(sumy[i]\) 也可以这么优化。

得到了 \(sumx\)\(sumy\) 后,可以通过枚举 \(x\) ,求出符合条件的 \(y\) 的个数,同时,使用 \(sumx\)\(sumy\) 快速计算 $\sum_{i = 1}^{N}\left|x - x_i\right| + \left|y-y_i\right| $ 。最后,统计所有符合条件的 \((x,y)\) 个数并输出。

code

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int x[200005],y[200005];
LL sumx[5000000],sumy[5000000];
void clac(int a[],LL sa[],int maxx,int n) {
    sort(a + 1,a + 1 + n);
    int it = 1;
    LL ps = 0,ss = 0;
    for(int i = 1;i <= n;i++) {
        ss += a[i];
    }
    for(int i = -maxx;i <= maxx;i++) {
        while(it <= n && a[it] < i) {
            ps += a[it];
            ss -= a[it++];
        }
        LL sum = 1LL * (it - 1) * i - ps + ss -  1LL * (n - it + 1) * i;
        sa[i + maxx] = sum;
    }
    sort(sa,sa + 2 * maxx + 1);
}
int main() {
    int n,d,maxx = 0;
    scanf("%d%d",&n,&d);
    for(int i = 1;i <= n;i++) {
        scanf("%d%d",&x[i],&y[i]);
        maxx = max(maxx,max(abs(x[i]),abs(y[i])));
    }
    clac(x,sumx,maxx + d,n);
    clac(y,sumy,maxx + d,n);
    LL ans = 0;
    int j = 2 * (maxx + d);
    for(int i = 0;i <= 2 * (maxx + d);i++) {
        while(j >= 0 && sumy[j] + sumx[i] > d) {
            j--;
        }
        ans += j + 1;
    }
    printf("%lld\n",ans);
    return 0;
}

F - Maximum Composition

题目大意

给定 \(2n\) 个整数 \(A_{1-n}\)\(B_{1-n}\) ,在 \(1\)\(n\) 中选出 \(k\) 个数 \(p = \lbrace p_1,\ p_2,\ ...,\ p_k\rbrace\) ,使得 \(f_1(f_2(...\ f_k(1)\ ...))\) 的值最大,其中 \(f_i(x)=A_ix+B_i\)

解题思路

考虑 \(f_i(f_j(1))\)\(f_j(f_i(1))\) 哪个大。

假设 \(f_i(f_j(1)) \geq f_j(f_i(1))\) ,则:

\[\begin{align*} A_i(A_j + B_j) + B_i &\geq A_j(A_i + B_i) + B_j \\ A_iB_j + B_i &\geq A_jB_i + B_j \\ A_iB_j - B_j &\geq A_jB_i - B_i \\ B_j(A_i - 1) &\geq B_i(A_j - 1) \end{align*}\]

即当 \(B_j(A_i - 1) \geq B_i(A_j - 1)\) 时, \(f_i(f_j(1)) \geq f_j(f_i(1))\) 。所以,可以按照这个规则进行排序,之后进行 01 背包。因为 01 背包是按照数组里物品的顺序进行选择的,所以排序后的 01 背包可以得到最大的值。

code

#include <bits/stdc++.h>
using namespace std;
using A = array<int, 2>;
A a[200005];
long long dp[15];
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &a[i][0], &a[i][1]);
    }
    sort(a + 1, a + 1 + n, [](const A &a, const A &b) {
        return (a[0] - 1) * b[1] < (b[0] - 1) * a[1];
    });
    dp[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = k; j >= 1; j--) {
            dp[j] =
                max(dp[j], a[i][0] * dp[j - 1] + a[i][1]);
        }
    }
    printf("%lld\n", dp[k]);
    return 0;
}

G - XOR Neighbors

题目大意

给定一个有 \(n\) 个节点,\(m\) 条边的简单无向图,需要在每一个节点上写一个介于 \(1\)\(2^{60}-1\) 的数,使得每一个节点的相邻节点异或和为 \(0\)

解题思路

定义一种“节点中的数”以便和“节点上写的数”区分。“节点中的数”只能是 \(0\)\(1\) ,且每一个节点的相邻节点中的数的异或和为 \(0\)

定义 \(ans_i\) 为第 \(i\) 个节点上面应该写什么数。为了让 \(ans_i\) 的值不为 \(0\) , 令 \(ans_i\) 的二进制第 \(v\) 位的意思为:如果第 \(v\) 个节点中的数为 \(1\) ,节点 \(i\) 中的数应该是多少。 \(ans_i\) 的表达方法可以参考状态压缩。

考虑如果第 \(v\) 个节点中的数为 \(1\) ,其他节点中的数是多少。

为了求出其他节点中的数,可以先根据数据构建一个方程组,第 \(i\) 个方程的意义为当第 \(i\) 个节点的相邻节点中的数异或和为 \(0\) 时,第 \(i\) 个节点的相邻节点中的数分别是多少。显然,这个构建出来的方程组将会有 \(n\) 个方程。比如输入数据:

3 3
1 2
1 3
2 3

所对应的方程组为:

\[\begin{cases} x_2 \oplus x_3 = 0 \\ x_1 \oplus x_3 = 0\\ x_1 \oplus x_2 = 0 \end{cases}\]

为了解出构建出来的方程组,可以使用高斯消元法,不懂高斯消元的可以看这里。上面这个方程组所对应的增广矩阵为:

\[\begin{bmatrix} 0&1&1&|&0 \\ 1&0&1&|&0 \\ 1&1&0&|&0 \end{bmatrix}\]

其中矩阵的第 \(a_{ij}\) 位也可以理解成节点 \(i\) 和节点 \(j\) 之间是否有边连接。在具体实现中可以利用这条性质生成增广矩阵。

为了让第 \(v\) 个节点中的数为 \(1\) ,可以在方程组里添加一个方程 $x_v = 1 $ 。在具体实现中,可以在增广矩阵数组的第 \(n+1\) 行的第 \(v\) 列和第 \(n+1\) 列设为 \(1\) ,从而达到在方程组添加方程的效果。

那么,添加这个方程对结果有影响吗?因为这个方程除了它所对应的行向量的第 \(v\) 位和增广矩阵增加的列向量的 \(n + 1\) 位为 \(1\) 外,其他位置均为 \(0\) ,所以它对其他的数没有影响;如果在处理过程中出现了一个 \(x_i = 0\) 与这个方程相对,则当 \(x_i = 1\) 时, \(x_i = 0\) ,所以 \(x_i\) 不可能等于 \(1\) ,即节点 \(i\) 中的数为 \(0\) ,这是不符合要求的,所以这个方程组无解,加不加这个方程都无所谓,所以添加这个方程对结果没有影响。

对于上面这个方程组,当 \(v = 2\) 时,方程组变成

\[\begin{cases} x_2 \oplus x_3 = 0 \\ x_1 \oplus x_3 = 0 \\ x_1 \oplus x_2 = 0 \\ x_2 = 1 \end{cases}\]

它所对应的增广矩阵为

\[\begin{bmatrix} 0&1&1&|&0 \\ 1&0&1&|&0 \\ 1&1&0&|&0 \\ 0&1&0&|&1 \end{bmatrix}\]

上面的增广矩阵高斯消元后的矩阵如下:

\[\begin{bmatrix} 1&0&0&|&1 \\ 0&1&0&|&1 \\ 0&0&1&|&1 \\ 0&0&0&|&0 \end{bmatrix}\]

因为方程组可能无解,但是高斯消元法不能够区分无解和多解,所以在完成高斯消元法后需要检验。如果增广矩阵的某一个行向量全部为 \(0\) ,且它在增广矩阵中增加的列向量中对应的一位为 \(1\) 时,即:

\[\begin{bmatrix} 0&...&0&|&1 \end{bmatrix}\]

由于它对应的方程为 \(0=1\) ,显然不成立,所以这个方程组无解,输出 No

如果方程组有解,则把增广矩阵增加的列向量中的结果用状态压缩的方法放入对应的节点的头上写的数的对应的二进制位中,再处理下一个二进制位。

注意: 由于可以填入的数的范围为 \(1\)\(2^{60} - 1\) ,超过了 int 的范围,所以应该使用 long long 。且因为最大可以填入的数为 \(2^{60}-1\) ,所以应该从位权为 \(2^0\) 的二进制位开始计算,否则结果将会超出范围。

code

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
LL ans[65];
LL h[65][65];
LL g[65][65];
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v; i <= m; i++) {
        scanf("%d%d", &u, &v);
        h[u][v] = h[v][u] = 1;
    }
    for (int v = 1; v <= n; v++) {
        memset(g, 0, sizeof(g));
        g[n + 1][v] = g[n + 1][n + 1] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                g[i][j] = h[i][j];
            }
        }
        for (int i = 1; i <= n; i++) {
            int cur = -1;
            for (int j = i; j <= n + 1; j++) {
                if (g[j][i] == 1) {
                    cur = j;
                }
            }
            if (cur == -1) continue;
            swap(g[cur], g[i]);
            for (int j = 1; j <= n + 1; j++) {
                if (i != j && g[j][i]) {
                    for (int k = 1; k <= n + 1; k++) {
                        g[j][k] ^= g[i][k];
                    }
                }
            }
        }
        for (int i = 1; i <= n + 1; i++) {
            int cnt = 0;
            for (int j = 1; j <= n; j++) {
                if (g[i][j]) cnt++;
            }
            if (cnt == 0 && g[i][n + 1]) {
                printf("No\n");
                return 0;
            } else if (g[i][n + 1]) {
                ans[i] += g[i][n + 1] << (v - 1);
            }
        }
    }
    printf("Yes\n");
    for (int i = 1; i <= n; i++) {
        printf("%lld ", ans[i]);
    }
    puts("");
    return 0;
}
posted @ 2024-08-12 16:33  sxl701817  阅读(153)  评论(0)    收藏  举报