ARC176 补

A 01 Matrix Again

求一个 \(n\times n\)\(01\) 矩阵,使得满足限制的情况下,每行每列都有 \(m\)\(1\)

限制:给定 \(m\) 个坐标,在这些坐标上的数必须是 \(1\)

Solution

思考一下怎么在全为 \(0\) 的矩阵中放 \(1\) 使得每行每列有且只有一个 \(1\)。不难想到对角线。

但是只有一种方案,显然是不够也无法满足条件的。注意到可以用两条斜线分别将其所在的每行每列 \(+1\)。加上对角线总共有 \(n\) 中方案,显然可以满足所有可能的 \(m\)。再注意一下发现其实一个方案所包含的格子就是 \((j-i+n) \mod n\) 相同的格子集合。

加上限制的话,可以看强制 \(1\) 的格子在哪个 \(+1\) 方案里,转化成强制使用这个方案。当然,可以有多个限制格在同一个方案中,所以如果每行每列 \(1\) 个数不到 \(m\) 的话还要随便补一点。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <set>

using namespace std;
using LL = long long;
using PII = pair<int, int>;

int n, m;
set<int> s;
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1, a, b; i <= m; i++) {
    cin >> a >> b, s.insert((b - a + n) % n);
  }
  for (int i = 0; s.size() < m; i++) {
    s.insert(i);
  }
  cout << n * m << '\n';
  for (auto d : s) {
    for (int i = 1; i <= n; i++) {
      cout << i << ' ' << (i + d - 1) % n + 1 << '\n';
    }
  }
  return 0;
}

B Simple Math 4

\(2^n\mod (2^m-2^k)\) 的个位。

Solution

分类讨论,但是大部分情况都是弱智情况,注意一下就好。这里只给出 \(n \geq m > k + 1\) 的情况。

约分掉 \(2^k\),令 \(a=n-k,b=m-k\),原式 \(=\frac{2^a}{2^b-1}\)

因为要求这个余数,而且一个是 \((10000...00)_2\),一个是 \((1111...11)_2\),不如竖式列出来看一眼。

先算一个位,\(2^a - 2^{a-b} \times (2^b-1) = 2^{a-b}\)。每除一位指数 \(-b\),直到无法再除,指数比 \(b\) 小时便结束,所以余数是 \(2^{a\mod b}\)。因为前面约分了一个 \(2^k\),算余数时要乘回来。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kP2[] = {6, 2, 4, 8};

LL T, n, m, k;
int main() {
  cin.tie(0)->sync_with_stdio(0);
  for (cin >> T; T--;) {
    cin >> n >> m >> k;
    if (m == k + 1) {
      cout << (n >= k ? 0 : kP2[n % 4]) << '\n';
    } else if (n < m) {
      cout << kP2[n % 4] << '\n';
    } else {
      cout << kP2[(n - k) % (m - k) % 4] * kP2[k % 4] % 10 << '\n';
    }
  }
  return 0;
}

C Max Permutation

求满足条件的 \(n\) 排列 \(p\) 数量。

条件:对于所有给定三元组 \((a_i,b_i,c_i)\),有 \(\max(p_{a_i},p_{b_i}) = c_i\)

Solution

我 IQ 不太够,所以只能分讨。

题面具象化一下,\(a_i\)\(b_i\) 连边表示 \(p\) 中位置 \(a_i\)\(b_i\) 中有一个等于 \(c_i\),另一个严格小于。

不如按 \(c_i\) 从大到小枚举点权处理贡献,算完了就删掉确定的点和边。这样子将数从大到小确定可以更好的考虑边限制。

手玩一下发现要将当前枚举到的点权分成三种情况讨论:

  • 不存在边权为这个点权的边。直接把这个点权赋给一个度为 \(0\) 的点。不存在就无解。

  • 有且仅有一条为这个点权的边。把这个点权赋给这条边上度为 \(1\) 的点,即仅连了这条边的点。不存在就无解。

  • 至少有两条为这个点权的边。若这些边组成一个菊花,且菊花的中心没有其他边权的边,则把点权赋给菊花中心。

证明非常简单。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 2e5 + 1, kP = 998244353;

int n, m, d[kN];
vector<PII> e[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1, u, v, w; i <= m; i++) {
    cin >> u >> v >> w;
    e[w].emplace_back(u, v);
    d[u]++, d[v]++;
  }
  int tot = count(d + 1, d + n + 1, 0), ans = 1;
  for (int i = n; i >= 1; i--) {
    if (e[i].empty()) {
      if (!tot) {
        cout << "0\n";
        return 0;
      }
      ans = 1ll * ans * tot % kP;
      tot--;
    } else if (e[i].size() == 1) {
      auto &[u, v] = e[i][0];
      d[u]--, d[v]--;
      if (d[u] > 0 && d[v] > 0) {
        cout << "0\n";
        return 0;
      }
      d[u] || d[v] || (ans = 2 * ans % kP, tot++);
    } else {
      int t = 0;
      auto [u, v] = e[i][0];
      auto [p, q] = e[i][1];
      if (u == p || u == q) {
        t = u;
      } else if (v == p || v == q) {
        t = v;
      } else if (!t) {
        cout << "0\n";
        return 0;
      }
      for (auto [x, y] : e[i]) {
        if (x != t && y != t) {
          cout << "0\n";
          return 0;
        }
      }
      for (auto [x, y] : e[i]) {
        x == t && (swap(x, y), 0);
        (--d[x]) || (tot++);
      }
      d[t] = 0;
    }
  }
  cout << ans << '\n';
  return 0;
}

D - Swap Permutation

给定一个 \(n\) 排列 \(p\),进行 \(m\) 次操作,求所有可能的操作方案所得序列权值和。

操作:任意选择排列中两数交换。

权值:排列中相邻两数差的绝对值的和。

Solution

可以考虑两个数在进行操作后可能发生的改变。

现在有两个数 \(a, b\)\(n-2\) 个其他的数 \(c\)。则它们可能组合出的状态有 \((a,b),(b,a),(a,c),(c,a),(c,b),(b,c),(c,c)\) 七种可能。

所以可以做一个 \(7*7\) 的转移矩阵,表示这其中状态之间的转化,暴力转移算贡献就好了。

也可以换一种方法考虑,把每个数拆成一个 01 序列,第 \(i\) 位表示这个数是否达到 \(i\)。那么两个数的 01 序列中不同的位数就是差了。

所以可以考虑 01 之间可能变成的状态,有 \((0,0),(1,0),(0,1),(1,0)\),但是 \((0,1),(1,0)\) 贡献相同可以合并考虑。

于是可以枚举一个阈值,每次加入阈值上的数,又可以矩乘计算贡献。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <array>
#include <atcoder/modint>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using namespace atcoder;
using LL = long long;
using mint = modint998244353;
using PII = pair<int, int>;
using mat = array<array<mint, 3>, 3>;
constexpr int kN = 2e5 + 1;

mat operator*(mat a, mat b) {
  mat ret = {{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}};
  for (int k = 0; k < 3; k++) {
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3; j++) {
        ret[i][j] += a[i][k] * b[k][j];
      }
    }
  }
  return ret;
}
mat P(mat a, int b) {
  mat ret = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};
  for (; b; b /= 2) {
    if (b % 2 == 1) {
      ret = ret * a;
    }
    a = a * a;
  }
  return ret;
}
int n, m, a[kN], p[kN];
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    p[a[i]] = i;
  }
  mint ans = 0;
  mat st{{{0, 0, n - 1}, {0, 0, 0}, {0, 0, 0}}};
  for (int i = 1; i < n; ++i) {
    if (p[i] > 1) {
      bool t = a[p[i] - 1] > i;
      st[0][t]++, st[0][t + 1]--;
    }
    if (p[i] < n) {
      bool t = a[p[i] + 1] > i;
      st[0][t]++, st[0][t + 1]--;
    }
    mat F{{{n * (n - 1ll) / 2 - 2 * (n - i), 2 * (n - i), 0},
           {i - 1, n * (n - 1ll) / 2 - n + 2, n - i - 1},
           {0, 2 * i, n * (n - 1ll) / 2 - 2 * i}}};
    ans += (st * P(F, m))[0][1];
  }
  cout << ans.val() << '\n';
  return 0;
}

E - Max Vector

给定两个长度为 \(n\) 的序列 \(x,y\),和一个 \(m\times n\) 矩阵 \(a\)。对于每个 \(i\in [1,m]\) 都进行一次操作,求最后 \(x,y\) 中所有数之和的最小值。

操作:选择 \(x\) 或者 \(y\),将其每一位替换为这一位与 \(a_i\) 相同位的最大值。

\(1\leq n\leq 10,1\leq m,V\leq 500\)

Solution

直接贪心,对于每次操作的选择,我直接看放哪边当前更好。

是错误的。因为当 \(a_1={V,V,0,0},a_2={0,V,V,0},a_3={0,0,V,V}\) 时,全给 \(x\) 或者全给 \(y\) 为最优,但是直接贪心会出现各种各样选择问题。

所以贪心没前途,看到两个操作必选一个,直接无脑最小割。暂时先不考虑 \(y\),先探究一下这个 \(a_{i,j}\)\(x_i\) 最终值的限制怎么考虑。

看到 \(n\)\(V\) 都非常的小,于是直接对于每个 \(x_i\) 建立一条长度为 \(V\) 的链,边权由 \(V\)\(1\) 递减,在 \(x_i\) 的链割掉边权为 \(l\) 的边代表着最终 \(x_i=l\)。源点向每条链底部连边,边权 \(\infty\),禁止割。至于 \(a_{i,j}\) 的限制,直接由源点向 \(x_j\) 链上第 \(a_{i, j}\) 个点连边权 \(\infty\) 的边禁止割即可。

那加入 \(y\) 又怎么办?

不如将 \(x\) 的所有东西反向,对于每个 \(y_i\) 建一条边权由 \(1\)\(V\) 递增的链,顶部连接汇点。重新考虑 \(a_{i,j}\) 限制。此时可以对于每个 操作 建立 一个 虚拟点,对于每个 \(j\)\(x_{j,a_{i,j}}\rightarrow op_i \rightarrow y_{j,a_{i,j}}\),边权 \(\infty\)。这样如果虚拟点被割到源点一侧说明第 \(i\) 次操作给了 \(x\),否则割到汇点一侧则给了 \(y\)

还要考虑上 \(x,y\) 初始值,直接源汇分别连对应值 \(\infty\) 边即可。

\(\\\)

Code
// STOOOOOOOOOOOOOOOOOOOOOOOOO hzt CCCCCCCCCCCCCCCCCCCCCCCORZ
#include <algorithm>
#include <atcoder/all>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;
using namespace atcoder;
using LL = long long;
using PII = pair<int, int>;
constexpr int kN = 10 + 1, kM = 500 + 1, kV = 501, kI = 1e9;

int n, m, X[kN], Y[kN], a[kM][kN];
int S = 0, T = 1, x[kN][kV + 1], y[kN][kV + 1], op[kM], cnt = 1;
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> X[i];
    iota(x[i], x[i] + kV + 1, cnt);
    cnt += kV;
  }
  for (int i = 1; i <= n; i++) {
    cin >> Y[i];
    iota(y[i], y[i] + kV + 1, cnt);
    cnt += kV;
  }
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
      cin >> a[i][j];
    }
    op[i] = ++cnt;
  }

  mf_graph<int> g(cnt + 1);
  auto A = [&](int x, int y, int w) { g.add_edge(x, y, w); };
  for (int i = 1; i <= n; i++) {
    A(S, x[i][kV], kI);
    A(y[i][kV], T, kI);
    for (int j = 1; j < kV; j++) {
      A(x[i][j + 1], x[i][j], j);
      A(y[i][j], y[i][j + 1], j);
    }
    A(x[i][X[i]], T, kI);
    A(S, y[i][Y[i]], kI);
  }
  for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
      A(x[j][a[i][j]], op[i], kI);
      A(op[i], y[j][a[i][j]], kI);
    }
  }
  cout << g.flow(S, T) << '\n';
  return 0;
}
posted @ 2024-07-20 15:46  Lightwhite  阅读(13)  评论(0)    收藏  举报