AtCoder Regular Contest 176 A~D

A.01 Matrix Again(思维)

题意:

有一个 N × N N \times N N×N 网格。让 ( i , j ) (i, j) (i,j) 表示从上往下第 i i i 行,从左往上第 j j j 列的单元格。

您需要在每个单元格中填入 0 0 0 1 1 1 。请构建一个满足以下所有条件的填充网格的方法:

  • 单元格 ( A 1 , B 1 ) , ( A 2 , B 2 ) … ( A M , B M ) (A_1,B_1),(A_2,B_2)\dots (A_M,B_M) (A1,B1),(A2,B2)(AM,BM) 中包含 1 1 1
  • i i i 行中的整数总和为 M M M ( 1 ≤ i ≤ N ) (1 \le i \le N) (1iN)
  • i i i 列中的整数总和为 M M M ( 1 ≤ i ≤ N ) (1 \le i \le N) (1iN)

可以证明,在这个问题的约束条件下,至少有一种填充网格的方法可以满足条件。

分析:

S k S_k Sk 是单元格 ( i , j ) (i,j) (i,j) 的集合,满足 i + j = k   m o d   N i + j = k \bmod N i+j=kmodN 。现在,如果我们从 S k S_k Sk 中选择 M M M 个不同的单元格,并在这些单元格中写入 1 1 1 ,则行和与列和将为 M M M
那么接下来就是为每个 i i i 加入 S A i + B i   m o d   N S_{A_i + B_i \bmod N} SAi+BimodN ,需要注意如果有重复的 A i + B i   m o d   N A_i + B_i \bmod N Ai+BimodN 中,则需要相应添加 S k S_k Sk

代码:

#include <bits/stdc++.h>

using namespace std;
int n, m;

int main() {
    int T;
    T = 1;
    while (T--) {
        cin >> n >> m;
        vector<bool> vis(n);
        for (int i = 1; i <= m; i++) {
            int x, y;
            cin >> x >> y;
            x--, y--;
            vis[(x + y) % n] = 1;
        }
        vector<int> ans;
        for (int i = 0; i < n; i++)
            if (vis[i])
                ans.push_back(i);
        for (int i = 0; i < n; i++)
            if (!vis[i] && ans.size() < m)
                ans.push_back(i);
        cout << n * m << endl;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int x = i, y = (ans[j] - i + n) % n;
                cout << x + 1 << " " << y + 1 << endl;
            }
        }
    }
    return 0;
}

B.Simple Math 4(数学)

题意:

2 N 2^N 2N 除以 2 M − 2 K 2^M - 2^K 2M2K 的余数的最后一位数字。

分析:

如果是 N ≥ M N \ge M NM ,我们可以变换 2 N ≡ 2 N − 2 N − M ( 2 M − 2 K ) ≡ 2 N − ( M − K ) (   m o d     2 M − 2 K ) 2^N \equiv 2^N - 2^{N-M}(2^M - 2^K) \equiv 2^{N-(M-K)} (\bmod\ 2^M - 2^K) 2N2N2NM(2M2K)2N(MK)(mod 2M2K) ,这样就可以把 N N N 换成 N − ( M − K ) N-(M-K) N(MK) 。重复这一操作,我们就可以将其简化为 N < M N < M N<M 的情况。
那么,如果是 N , K = M − 1 N,K = M-1 N,K=M1 ,我们就有 2 N = 2 M − 2 K 2^N = 2^M - 2^K 2N=2M2K ,所以答案是 0 0 0 。否则,有 2 N < 2 M − 2 K 2^N < 2^M - 2^K 2N<2M2K ,所以答案是 2 N   m o d   10 2^N \bmod 10 2Nmod10

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int mod = 998244353;

LL binpow(LL a, LL b, LL m) {
    a %= m;
    LL res = 1;
    while (b > 0) {
        if (b & 1)
            res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

LL n, m;

int main() {
    int T;
    cin >> T;
    while (T--) {
        LL k;
        cin >> n >> m >> k;
        n -= max(0ll, n - k) / (m - k) * (m - k);
        if (n == m - 1 && k == m - 1)
            cout << "0" << endl;
        else
            cout << binpow(2, n, 10) << endl;
    }
    return 0;
}

C.Max Permutation(图论)

题意:

输出满足以下条件的 ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,,N) 的排列 P = ( P 1 , P 2 , … , P N ) P=(P_1,P_2,\dots,P_N) P=(P1,P2,,PN) 的数量,并将答案对 998244353 998244353 998244353 取模。

  • max ⁡ ( P A i , P B i ) = C i   ( 1 ≤ i ≤ M ) \max(P_{A_i},P_{B_i}) = C_i\ (1 \le i \le M) max(PAi,PBi)=Ci (1iM) .

分析:

我们构建一个有 N N N 个顶点的图 G G G ,其中每个 i i i 顶点 A i A_i Ai B i B_i Bi 之间都有一条权重为 C i C_i Ci 的边。

如果有 max ⁡ ( P A i , P B i ) = C i \max(P_{A_i},P_{B_i}) = C_i max(PAi,PBi)=Ci ,那么必须有 P A i , P B i ≤ C i P_{A_i},P_{B_i} \le C_i PAi,PBiCi 。据此,对于每一个 i i i 我们都可以推导出一个形式为 P i ≤ X i P_i \le X_i PiXi 的条件。(如果顶点 i i i G G G 中的一个孤立点,那么 X i = ∞ X_i = \infty Xi= )。

假设 P i = k P_i = k Pi=k 并且我们按照 k = N , N − 1 , . . . , 1 k = N,N-1,...,1 k=N,N1,...,1的顺序依次处理 。在此过程中,我们进行以下情况的区分。

  • 如果有两个或两个以上的 i i i 满足 C i = k C_i = k Ci=k。那么所有权重为 k k k 的边都必须有一个顶点 v v v 作为端点。这里需要注意的是,如果存在这样一个顶点 v v v ,那么它就是唯一确定的。如果 v v v 不存在或 X v < k X_v < k Xv<k 不存在,那么答案就是 0 0 0 。否则,我们设为 P v = k P_v = k Pv=k
  • 如果只有一个 i i i 满足 C i = k C_i = k Ci=k。则在该边的端点中选择一个 X j ≥ k X_j \ge k Xjk 并设置 P j = k P_j = k Pj=k 。如果两个端点都满足条件,那么无论我们选择哪一个,情况都是一样的,因此我们将答案乘以 2 2 2 并设置 P j = k P_j = k Pj=k 。如果两个端点都不符合条件,答案就是 0 0 0
  • 如果没有 i i i满足 C i = k C_i = k Ci=k。我们从 X j ≥ k X_j \ge k Xjk P j P_j Pj 尚未确定的顶点中选择一个顶点,并设置为 P j = k P_j = k Pj=k 。如果不存在这样的顶点,答案就是 0 0 0 。如果存在多个这样的顶点,那么无论我们选择哪个顶点,情况都是一样的,我们将答案乘以候选顶点的数量,并为某个顶点设置 P j = k P_j = k Pj=k

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 10;
const int MOD = 998244353;
int n, m;
int tmp[MAXN];
int a[MAXN];
int u, v, w;
vector<pair<int, int> > e[MAXN];
LL ans = 1, res;

int main() {
    int T;
    T = 1;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) {
            tmp[i] = n;
        }
        for (int i = 0; i < m; i++) {
            cin >> u >> v >> w;
            e[w].push_back(make_pair(u, v));
            tmp[u] = min(tmp[u], w);
            tmp[v] = min(tmp[v], w);
        }
        for (int i = 1; i <= n; i++) {
            a[tmp[i]]++;
        }
        for (int i = n; i >= 1; i--) {
            res += a[i];
            if (e[i].size() >= 2) {
                int tmp1 = -1;
                if (e[i][0].first == e[i][1].first || e[i][0].first == e[i][1].second) {
                    tmp1 = e[i][0].first;
                } else if (e[i][0].second == e[i][1].first || e[i][0].second == e[i][1].second) {
                    tmp1 = e[i][0].second;
                } else {
                    ans = 0;
                }
                for (int j = 2; j < e[i].size(); j++) {
                    if (e[i][j].first != tmp1 && e[i][j].second != tmp1) {
                        ans = 0;
                    }
                }
                if (tmp[tmp1] < i) {
                    ans = 0;
                }
                res--;
            } else if (e[i].size() == 1) {
                int num = 0;
                if (tmp[e[i][0].first] >= i) {
                    num++;
                }
                if (tmp[e[i][0].second] >= i) {
                    num++;
                }
                ans = (ans * num) % MOD;
                if (num) {
                    res--;
                }
            } else {
                ans = (ans * res) % MOD;
                if (res) {
                    res--;
                }
            }
        }
        cout << ans << endl;
    }
    return 0;
}

D.Swap Permutation (数学)

题意:

给你一个 ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,,N) 的排列组合 P = ( P 1 , P 2 , … , P N ) P=(P_1,P_2,\dots,P_N) P=(P1,P2,,PN) 。你将执行以下操作 M M M 次:

  • 选择一对整数 ( i , j ) (i, j) (i,j) ,使得 1 ≤ i < j ≤ N 1 \le i < j \le N 1i<jN ,然后交换 P i P_i Pi P j P_j Pj

( N ( N − 1 ) 2 ) M \left(\frac{N(N-1)}{2}\right)^M (2N(N1))M 个可能的操作序列。对于其中的每一个,考虑所有运算后的值 ∑ i = 1 N − 1 ∣ P i − P i + 1 ∣ \sum\limits_{i=1}^{N-1} |P_i - P_{i+1}| i=1N1PiPi+1 。求所有这些值的和,并对 998244353 998244353 998244353取模 。

分析:

将问题转化成:把 P P P 中小于或等于 k k k 的值替换为 0 0 0 ,将大于 k k k 的值替换为 1 1 1 ,再计算 ∑ i = 1 N − 1 ∣ P i − P i + 1 ∣ \sum\limits_{i=1}^{N-1} |P_i - P_{i+1}| i=1N1PiPi+1 的和,并将所有 k ( 1 ≤ k ≤ N − 1 ) k(1 \le k \le N-1) k(1kN1) 的和相加。

接下来解决 P P P 的所有元素都是 0 0 0 1 1 1 的问题。考虑找出每个 i i i 在操作后 ∣ P i − P i + 1 ∣ = 1 |P_i - P_{i+1}| = 1 PiPi+1=1 的序列数量。这可以通过对三个状态进行矩阵指数运算来实现,三个状态分别代表 P i P_i Pi P i + 1 P_{i+1} Pi+1 中有 j ( 0 ≤ j ≤ 2 ) j(0 \le j \le 2) j(0j2) 个零。

对所有 i , k i,k i,k 进行上述运算的时间复杂度为 O ( N 2 log ⁡ M ) \mathrm{O}(N^2 \log M) O(N2logM) ,但如果使用累积和或类似方法,事先计算出每个 k k k i i i 的个数,使得初始状态中 P i P_i Pi P i + 1 P_{i+1} Pi+1 中不大于 k k k 的元素个数为 j j j ,就可以在 O ( log ⁡ M ) \mathrm{O}(\log M) O(logM) 中找到每个 k k k 的答案。

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 2e5 + 10;
const int mod = 998244353;

struct MAT {
    int z[3][3];

    MAT() {
        memset(z, 0, sizeof(z));
    }

    MAT operator*(MAT y) {
        MAT x = *this, ans;
        for (int i = 0; i < 3; ++i) {
            for (int k = 0; k < 3; ++k) {
                for (int j = 0; j < 3; ++j) {
                    ans.z[i][j] = (ans.z[i][j] + 1ll * x.z[i][k] * y.z[k][j]) % mod;
                }
            }
        }
        return ans;
    }

    MAT operator^(int y) {
        MAT x = *this, ans;
        for (int i = 0; i < 3; ++i) {
            ans.z[i][i] = 1;
        }
        while (y) {
            if (y & 1) {
                ans = ans * x;
            }
            x = x * x;
            y >>= 1;
        }
        return ans;
    }
};

int p[maxn], v[maxn], c[4];

int main() {
    int T;
    T = 1;
    while (T--) {
        int n, m;
        cin >> n >> m;
        for (int i = 1, x; i <= n; ++i) {
            cin >> x;
            p[x] = i;
        }
        int ans = 0;
        c[0] = n - 1;
        for (int i = 1; i < n; ++i) {
            int t = p[i];
            if (t != 1) {
                --c[(v[t - 1] << 1) | v[t]];
            }
            if (t != n) {
                --c[(v[t] << 1) | v[t + 1]];
            }
            v[t] = 1;
            if (t != 1) {
                ++c[(v[t - 1] << 1) | v[t]];
            }
            if (t != n) {
                ++c[(v[t] << 1) | v[t + 1]];
            }
            MAT tmp;
            tmp.z[0][0] = (1ll * n * (n - 1) / 2 - 2 * i) % mod;
            tmp.z[0][1] = 2 * i;
            tmp.z[1][0] = n - i - 1;
            tmp.z[1][1] = (1ll * n * (n - 1) / 2 - (n - i - 1) - (i - 1)) % mod;
            tmp.z[1][2] = i - 1;
            tmp.z[2][1] = (n - i) * 2;
            tmp.z[2][2] = (1ll * n * (n - 1) / 2 - (n - i) * 2) % mod;
            MAT tn = tmp ^ m;
            ans = (ans + 1ll * tn.z[0][1] * c[0] % mod + 1ll * tn.z[1][1] * (c[1] + c[2]) % mod +
                   1ll * tn.z[2][1] * c[3] % mod) % mod;
        }
        cout << ans << endl;
    }
    return 0;
}

赛后交流

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值