🦁
ABC348Fのbitset解について
ABC348Fのbitset解がよくわからなかった
bitsetを用いた問題を解けたことがなく、解説を見ても意味不明だったので、少しずつステップを踏んで解説を理解することにした。
愚直解
void solve(){
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m){
cin >> a[i][j];
}
int ans = 0;
rep(i, n){
for(int j =i+1;j < n;j++){
int cnt = 0;
rep(k, m){
if(a[i][k] == a[j][k])cnt++;
}
if(cnt % 2){
ans++;
}
}
}
cout << ans << endl;
}
まずは愚直解から始めてみる。さすがにここはわかる。
ansの表現を行列の形式に寄せる
ansが何を表しているのかよくわからなかったので、出力パートから逆算してみた。
void solve(){
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m){
cin >> a[i][j];
}
vector ans(n, vector(n, false));
rep(i, n){
rep(j, n){
int cnt = 0;
rep(k, m){
if(a[i][k] == a[j][k])cnt++;
}
if(cnt % 2){
ans[i][j] = true;
}
}
}
int ans_val = 0;
rep(i, n){
rep(j, n){
ans_val += ans[i][j];
}
if(m%2){
// mが奇数の場合、i番目とi番目について、X_i = X_i となる個数が奇数となる
ans_val--;
}
}
cout << ans_val / 2 << endl;
}
ansをbitsetに変更
やるだけ。
void solve(){
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m){
cin >> a[i][j];
}
vector<bitset<2000>> ans(n);
rep(i, n){
rep(j, n){
int cnt = 0;
rep(k, m){
if(a[i][k] == a[j][k])cnt++;
}
if(cnt % 2){
ans[i].set(j);
}
}
}
int ans_val = 0;
rep(i, n){
ans_val += ans[i].count();
if(m%2){
// mが奇数の場合、i番目とi番目について、X_i = X_i となる個数が奇数となる
ans_val--;
}
}
cout << ans_val / 2 << endl;
}
解説のアルゴリズム
具体的には、
-
番目に値j が登場するX の集合i appeared
(解説 のbs
) を で計算するO(N) - 各
について、i(i = 1, 2, \cdots , N) の状態を更新するans_{i, k} -
が「似ている個数」を持ってると考えたとき、要素数分足す必要があり、これだとTLE(愚直解)ans_{i, k} - 必要なのが最終的な「似ている個数の 偶奇 」であればいいことを考えると、これは bitwise XOR で加算を表現できる。(この時に、bitsetの性質で約 64 倍高速化することが可能となっている)
-
void solve(){
int n, m;
cin >> n >> m;
vector<vector<int>> a(n, vector<int>(m));
rep(i, n)rep(j, m){
cin >> a[i][j];
}
// ans[i] := j 番目まで見たときに、 i番目と同じ数の項の個数の偶奇が奇数となっている各A_k (k = 1, 2, ..., N)について、k番目のビットが立っている
vector<bitset<2000>> ans(n);
rep(j, m){
vector<bitset<2000>> appeared(1000);
rep(i, n){
appeared[a[i][j]].set(i);
}
rep(i, n){
ans[i] ^= appeared[a[i][j]];
}
}
int ans_val = 0;
rep(i, n){
ans_val += ans[i].count();
if(m%2){
// mが奇数の場合、i番目とi番目について、X_i = X_i となる個数が奇数となる
ans_val--;
}
}
cout << ans_val / 2 << endl;
}
感想
コンテスト直後は、bitsetを知らなかったから解かなかったものだと思い込んでいたけれども、「どの状態をどのように表現するか」について考えられないと解けないものだと分かった(単に劇薬的なアルゴリズムではなかった点については少しだけ安心できた?)。
Discussion