結果
| 問題 |
No.1773 Love Triangle
|
| コンテスト | |
| ユーザー |
hitonanode
|
| 提出日時 | 2021-12-08 01:14:41 |
| 言語 | C++17(clang) (17.0.6 + boost 1.87.0) |
| 結果 |
AC
|
| 実行時間 | 1,328 ms / 2,000 ms |
| コード長 | 5,379 bytes |
| コンパイル時間 | 1,221 ms |
| コンパイル使用メモリ | 133,232 KB |
| 実行使用メモリ | 6,948 KB |
| 最終ジャッジ日時 | 2024-07-08 05:13:23 |
| 合計ジャッジ時間 | 62,128 ms |
|
ジャッジサーバーID (参考情報) |
judge5 / judge4 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 90 |
ソースコード
// 構築つき O(N^3 + N^2 M)
#include <algorithm>
#include <cassert>
#include <iostream>
#include <tuple>
#include <vector>
using namespace std;
#include <atcoder/modint>
using mint = atcoder::modint1000000007;
template <int md> std::ostream &operator<<(std::ostream &os, const atcoder::static_modint<md> &x) {
return os << x.val();
}
uint32_t rand_int() // XorShift random integer generator
{
static uint32_t x = 123456789, y = 362436069, z = 521288629, w = 88675123;
uint32_t t = x ^ (x << 11);
x = y;
y = z;
z = w;
return w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
}
// 行列ランク 掃き出し,O(n^3)
template <class T> int rank_of_matrix(std::vector<std::vector<T>> M) {
const int N = M.size();
int rank = 0;
for (int i = 0; i < N; ++i) {
int ti = i;
while (ti < N and M[ti][i] == 0) ti++;
if (ti == N) continue;
++rank;
M[i].swap(M[ti]);
T inv = T(1) / M[i][i];
for (int j = i + 1; j < N; ++j) M[i][j] *= inv;
for (int h = 0; h < N; ++h) {
if (i == h) continue;
const T c = -M[h][i];
for (int j = i + 1; j < N; ++j) M[h][j] += M[i][j] * c;
}
}
return rank;
}
// 逆行列 掃き出し,O(n^3)
// int でランクを返す
template <class T> int inverse_matrix(std::vector<std::vector<T>> &M) {
const int N = M.size();
assert(N and M[0].size() == M.size());
std::vector<std::vector<T>> ret(N, std::vector<T>(N));
for (int i = 0; i < N; ++i) ret[i][i] = 1;
int rank = 0;
for (int i = 0; i < N; ++i) {
int ti = i;
while (ti < N and M[ti][i] == 0) ti++;
if (ti == N) { continue; }
++rank;
ret[i].swap(ret[ti]), M[i].swap(M[ti]);
T inv = T(1) / M[i][i];
for (int j = 0; j < N; ++j) ret[i][j] *= inv;
for (int j = i + 1; j < N; ++j) M[i][j] *= inv;
for (int h = 0; h < N; ++h) {
if (i == h) continue;
const T c = -M[h][i];
for (int j = 0; j < N; ++j) ret[h][j] += ret[i][j] * c;
for (int j = i + 1; j < N; ++j) M[h][j] += M[i][j] * c;
}
}
M = ret;
return rank;
}
// H. Y. Cheung, L. C. Lau, and K. M. Leung, "Algebraic algorithms for linear matroid parity problems,"
// ACM Transactions on Algorithms, 10(3), pp.1–26, 2014.
//
// Algorithmn 4.1 で,特に考えている線形マトロイドが閉路マトロイドで
// 一端点を共有する二辺のペアたちに関する重みなし線形マトロイドパリティのケースの実装
int main() {
cin.tie(nullptr), ios::sync_with_stdio(false);
int N, M;
cin >> N >> M;
if (N % 2) ++N; // 奇数次の歪対称行列はフルランクにならないので偶数次にします
vector<tuple<int, int, int, int>> triangles(M);
vector mat(N, vector<mint>(N));
for (auto &[a, b, c, x] : triangles) {
cin >> a >> b >> c;
--a, --b, --c;
x = rand_int() % mint::mod();
mat[a][b] += x;
mat[b][c] += x;
mat[c][a] += x;
mat[b][a] -= x;
mat[c][b] -= x;
mat[a][c] -= x;
}
// フルランクでない行列は扱いが面倒なのでランク 2 ランダム更新を課して
// 歪対称性を保ったままフルランクにする
for (int r = rank_of_matrix(mat); r < N; r += 2) {
vector<mint> y(N), z(N);
for (auto &x : y) x = rand_int() % mint::mod();
for (auto &x : z) x = rand_int() % mint::mod();
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
mat[i][j] += y[i] * z[j] - z[i] * y[j];
}
}
}
auto Minv = mat;
assert(inverse_matrix(Minv) == N);
vector<int> I(M, 1);
for (int i = 0; i < M; ++i) {
auto [a, b, c, x] = triangles[i];
// ベクトルの wedge 積の足し合わせで作った M から,ランクを落とさないようにベクトルの wedge 積をそっと引いていく.
// ランクの減少を効率的に検知するために,M の逆行列を常に保持し,Sherman-Morrison-Woodbury formula を活用する.
// Sherman-Morrison-Woodbury formula に出てくる 2x2 行列の左上成分
// (この 2x2 行列は実は本問題の場合必ず単位行列の定数倍になる)
mint A00 = 1 + x * (Minv[a][b] + Minv[b][c] + Minv[c][a]);
if (A00 == 0) {
// i 個目のベクトル対を消すとランクが落ちてしまうのでこれは必要不可欠
I[i] = 1;
} else {
// i 個目のベクトル対を消しても M のランクが落ちないのでこれはなくてもいい,消す
I[i] = 0;
mint coeff = x / A00;
vector<mint> MinvB(N), MinvC(N);
for (int i = 0; i < N; ++i) {
MinvB[i] = Minv[b][i] - Minv[a][i];
MinvC[i] = Minv[c][i] - Minv[b][i];
}
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
Minv[i][j] -= (MinvB[i] * MinvC[j] - MinvB[j] * MinvC[i]) * coeff;
}
}
}
}
// I[i] = 1 -> 第 i 要素を採用する,I[i] = 0 -> 第 i 要素を捨てる
cout << accumulate(I.begin(), I.end(), 0) << '\n';
}
hitonanode