結果
問題 |
No.2251 Marking Grid
|
ユーザー |
![]() |
提出日時 | 2025-02-01 12:34:47 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 755 ms / 3,000 ms |
コード長 | 3,827 bytes |
コンパイル時間 | 5,494 ms |
コンパイル使用メモリ | 216,056 KB |
実行使用メモリ | 45,620 KB |
最終ジャッジ日時 | 2025-02-01 12:35:01 |
合計ジャッジ時間 | 13,338 ms |
ジャッジサーバーID (参考情報) |
judge4 / judge1 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 2 |
other | AC * 40 |
ソースコード
#include <bits/stdc++.h> using namespace std; const long long MOD = 998244353; // Union-Find (Disjoint Set Union) の実装 struct DSU { int n; vector<int> parent, rank; DSU(int n): n(n), parent(n), rank(n, 0) { for (int i = 0; i < n; i++) parent[i] = i; } int find(int a) { if(parent[a] == a) return a; return parent[a] = find(parent[a]); } bool merge(int a, int b) { a = find(a), b = find(b); if(a == b) return false; if(rank[a] < rank[b]) swap(a, b); parent[b] = a; if(rank[a] == rank[b]) rank[a]++; return true; } }; // 累乗 (mod MOD) long long modexp(long long base, long long exp){ long long result = 1; base %= MOD; while(exp > 0){ if(exp & 1) result = (result * base) % MOD; base = (base * base) % MOD; exp >>= 1; } return result; } // 辺の構造体:セル (i,j) に対応. struct Edge { int u, v; // u: 行 (0〜H-1), v: 列 (内部的には H〜H+W-1) int w; // セルに書かれている値 }; int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int H, W; cin >> H >> W; vector<vector<int>> A(H, vector<int>(W)); vector<int> candVals; vector<Edge> edges; for (int i = 0; i < H; i++){ for (int j = 0; j < W; j++){ cin >> A[i][j]; candVals.push_back(A[i][j]); Edge e; e.u = i; e.v = H + j; // 列の頂点番号は H から始まる e.w = A[i][j]; edges.push_back(e); } } // 重複を除いた候補値 sort(candVals.begin(), candVals.end()); candVals.erase(unique(candVals.begin(), candVals.end()), candVals.end()); // 辺を重み (セルの値) の降順にソート sort(edges.begin(), edges.end(), [](const Edge &a, const Edge &b){ return a.w > b.w; }); // 候補値も降順・昇順両方で使うので,ここでは降順版も作っておく vector<int> candDesc = candVals; sort(candDesc.begin(), candDesc.end(), greater<int>()); int totalVertices = H + W; DSU dsu(totalVertices); int comp = totalVertices; // 初期は各頂点が独立 int eidx = 0; // 各候補 T に対して,グラフ(セルの値 > T の辺による)の連結成分数を記録 unordered_map<int,int> compFor; for (int T : candDesc) { // 辺リストから,重みが T より大きい辺を追加 while(eidx < edges.size() && edges[eidx].w > T){ if(dsu.merge(edges[eidx].u, edges[eidx].v)){ comp--; } eidx++; } compFor[T] = comp; } // 事前に 2 のべき乗を 0 〜 totalVertices まで計算しておく vector<long long> pow2(totalVertices+1, 1); for (int i = 1; i <= totalVertices; i++){ pow2[i] = (pow2[i-1] * 2) % MOD; } // f(T) = 2^(連結成分数 - 1) - 1 auto f = [&](int T) -> long long { int c = compFor[T]; long long ways = (pow2[c - 1] - 1) % MOD; if(ways < 0) ways += MOD; return ways; }; // 印の付け方のうち「すべての印が T 以下」であるものの個数は f(T) // よって,最大値が正確に T となる印の付け方の個数は,(f(T) - f(prev)) となる. // 最小値より下では f = 0 とみなせる. long long ans = 0; long long prevF = 0; // 候補を昇順に処理 sort(candVals.begin(), candVals.end()); for (int T : candVals) { long long curF = f(T); long long diff = (curF - prevF) % MOD; if(diff < 0) diff += MOD; ans = (ans + (1LL * T * diff) % MOD) % MOD; prevF = curF; } cout << ans % MOD << "\n"; return 0; }