C - Submask Editorial by physics0523
以下の解説で、 \(2\) 進表記での \(2^k\) の位を \(k\) bit 目と呼びます。
解法1. 集合を徐々に大きくして構成する
以下を順次行うことにより、答えを構成することができます。
- 最初、 \(S=\{0\}\) とする。
- \(i=0,1,\dots,59\) まで以下を繰り返す。
- もし \(N\) の \(i\) bit 目が \(1\) なら、以下の更新を行う。
- \(S\) の全ての要素 \(x\) について同時に、 \(x\) の \(i\) bit 目を \(1\) にした新たな要素 \(y\) を追加する。
- もし \(N\) の \(i\) bit 目が \(1\) なら、以下の更新を行う。
実装例(C++):
#include<bits/stdc++.h>
using namespace std;
int main(){
long long x;
cin >> x;
vector<long long> res={0};
for(int d=0;d<60;d++){
if(x&(1ll<<d)){
int sz=res.size();
for(int i=0;i<sz;i++){
res.push_back(res[i]|(1ll<<d));
}
}
}
sort(res.begin(),res.end());
for(auto &nx : res){cout << nx << "\n";}
return 0;
}
Bonus: 実装例 \(17\) 行目 ( sort
) を丸ごと削除してもこのコードは AC します。理由を考えてみましょう。
解法2. bit 全探索の要領で構成する
はじめに、 \(N\) のうち何 bit 目が \(1\) であるかを列挙しておきます。 ( 列挙した数列を \(A=(A_1,A_2,\dots,A_k)\) とします )
その後、 \(1\) である bit の数を \(k\) として \(0\) から \(2^k-1\) までの整数 \(i\) でループを回します。
このとき、 \(i\) の \(j\) bit 目が \(1\) なら \(A_j\) bit 目を立てるようにすると、目的の集合を得ることができます。
実装例(C++):
#include<bits/stdc++.h>
using namespace std;
int main(){
long long n;
cin >> n;
vector<int> a;
for(int i=0;i<60;i++){
if(n&(1ll<<i)){a.push_back(i);}
}
int k=a.size();
vector<long long> res;
for(int i=0;i<(1<<k);i++){
long long cur=0;
for(int j=0;j<k;j++){
if(i&(1<<j)){cur|=(1ll<<a[j]);}
}
res.push_back(cur);
}
sort(res.begin(),res.end());
for(auto &nx : res){cout << nx << "\n";}
return 0;
}
Bonus: 実装例 \(21\) 行目 ( sort
) を丸ごと削除してもこのコードは AC します。理由を考えてみましょう。
解法3. ループを上手く回す
この問題を bit 列で集合を扱う問題に言い換えると、ある集合の部分集合全体を渡る問題になります。実装例のようにループを回せばこの問題に正解できます。
(部分集合を値の大きい方から渡るので、出力する際に逆順にする必要があることに注意してください。)
以下の日本語の記事には、他にも bit 列で集合を扱う際のテクニックが掲載されています。
ビット列による部分集合表現 【ビット演算テクニック Advent Calendar 2016 1日目】
実装例(C++):
#include<bits/stdc++.h>
using namespace std;
int main(){
long long x;
cin >> x;
vector<long long> res;
for(long long i=(1ll<<60)-1; i>=0; i--){
i&=x;
res.push_back(i);
}
reverse(res.begin(),res.end());
for(auto &nx : res){cout << nx << "\n";}
return 0;
}
posted:
last update: