第4回 全探索, 深さ優先探索, 幅優先探索
担当 : zeke
KMC-ID : zeke
slack(内部チャット)
#zeke_memoもよろしく!
ささっと、やりましょう!
全探索
順列全探索
深さ優先探索
幅優先探索
bit 全探索(おまけ)
→
→
→
→
→
...
...
int main() {
vector<int> vec={1,0,3,2,4};
sort(vec.begin(),vec.end());//まず配列をソートしましょう
do{
for(auto i:vec){//こういう書き方もあります
cout<<i<<" ";
}
cout<<endl;
}while(next_permutation(vec.begin(),vec.end()));
//do-while文というものです。whileの中身を実行してから()内を評価します。
}サンプルコード
/*
Author:zeke
pass System Test!
GET AC!!
*/
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <deque>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
using ll = long long;
using ld = long double;
using namespace std;
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
#define all(x) (x).begin(), (x).end()
#define rep3(var, min, max) for (ll(var) = (min); (var) < (max); ++(var))
#define repi3(var, min, max) for (ll(var) = (max)-1; (var) + 1 > (min); --(var))
#define Mp(a, b) make_pair((a), (b))
#define F first
#define S second
#define Icin(s) \
ll(s); \
cin >> (s);
#define Scin(s) \
ll(s); \
cin >> (s);
template <class T>
bool chmax(T& a, const T& b) {
if (a < b) {
a = b;
return 1;
}
return 0;
}
template <class T>
bool chmin(T& a, const T& b) {
if (b < a) {
a = b;
return 1;
}
return 0;
}
typedef pair<ll, ll> P;
typedef vector<ll> V;
typedef vector<V> VV;
typedef vector<P> VP;
ll mod = 1e9 + 7;
ll MOD = 1e9 + 7;
ll INF = 1e18;
// cout << "Case #" << index << " :IMPOSSIBLE";
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cout << fixed << setprecision(10);
ll n,m,q;
cin>>n>>m>>q;
VV query(q,V(4));
rep(i,q){
rep(j,4){
cin>>query[i][j];
}
}
V vec;
rep(i,m-1){
vec.push_back(1);
}
rep(i,n){
vec.push_back(0);
}
sort(all(vec));
ll res=0;
do{
ll reg=1;
ll tempres=0;
V temp;
for(auto i:vec){
if(i==0){
temp.push_back(reg);
}else{
reg++;
}
}
rep(i,q){
if(temp[query[i][1]-1]-temp[query[i][0]-1]==query[i][2]){
tempres+=query[i][3];
}
}
chmax(res,tempres);
}while(next_permutation(all(vec)));
cout<<res<<endl;
}
next_permutationは重複した配列を返さないことを利用
再帰を使ったやり方(簡単)
vector<vector<int> > graph; //graph[v]には頂点v につながっている
//頂点の集合が入っているとする
vector<int> vec;
void dfs(int v){ //v に関して深さ優先探索(vは頂点の番号)
vec[v]=true; //v に訪問済みの印を付ける
/* v を処理する */
for(int i : graph[v]){ //v に接続している各頂点i に対し
if(vec[i]==false){ //i が未訪問なら
dfs(i); //i に関して深さ優先探索
}
}
}スタックを使ったやり方
void dfs(int z){ //z から始める深さ優先探索(zは頂点の番号)
stack<int> S; //空のスタックS を用意
vec[z] = true; //z に訪問済みの印を付ける
S.push(z); //z をスタックS に積む
while(!S.empty()){ //スタックS が空でなければ
int v = S.top(); //スタックS の一番上にある要素を取り出す
S.pop(); //スタックS の一番上にある要素を削除
/* v を処理する */
for(int i : graph[v]){ //v に接続している各頂点i に対し
if(vec[i]==false){ //i が未訪問なら
vec[i] = true; //i に訪問済みの印を付ける
S.push(i); //i をスタックS に積む
}
}
}
}vector<vector<ll>> vec;
ll N, M, Q;
ll res=0;
void dfs(string s){
if(s.size()==N+1){
ll tempres=0;
rep(i,Q){
if ((s[vec[i][1]]-'0')-(s[vec[i][0]]-'0')==vec[i][2]) {
tempres += vec[i][3];
}
}
chmax(res,tempres);
return;
}
for(int i=s.back()-'0';i<=M;i++){
dfs(s+(char)(i+'0'));
}
}
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cout << fixed << setprecision(10);
cin>>N>>M>>Q;
vec.resize(Q,vector<ll>(4));
rep(i,Q){
rep(j,4){
cin>>vec[i][j];
}
}
dfs("1");
cout<<res<<endl;
}
キューを使ったやり方
void bfs(int z){ //z から始める幅優先探索(zは頂点の番号)
queue<int> Q; //空のキューQ を用意
vec[z] = true; //z に訪問済みの印を付ける
Q.push(z); //z をキューQ に追加
while(!S.empty()){ //キューQ が空でなければ
int v = Q.front(); //キューQ の一番上にある要素を取り出す
Q.pop(); //キューQ の一番上にある要素を削除
/* v を処理する */
for(int i : graph[v]){ //v に接続している各頂点i に対し
if(vec[i]==false){ //i が未訪問なら
vec[i] = true; //i に訪問済みの印を付ける
S.push(i); //i をキューQ に追加
}
}
}
}BFSでも(ry
vector<vector<ll>> vec;
ll N, M, Q;
ll res=0;
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cout << fixed << setprecision(10);
cin>>N>>M>>Q;
vec.resize(Q,vector<ll>(4));
rep(i,Q){
rep(j,4){
cin>>vec[i][j];
}
}
queue<string> q;
q.push("1");
while(!q.empty()){
string s=q.front();
q.pop();
if(s.size()==N+1){
ll tempres=0;
rep(i,Q){
if ((s[vec[i][1]]-'0')-(s[vec[i][0]]-'0')==vec[i][2]) {
tempres += vec[i][3];
}
}
chmax(res,tempres);
continue;
}
for(int i=s.back()-'0';i<=M;i++){
q.push(s+(char)(i+'0'));
}
}
cout<<res<<endl;
}
ビット
バイト
2進数表記
例:142 2進数 0b10001110
16進数 0x8E
8進数 0216
補数
基準 ①よって求められた値を2進数では『1の補数』
基準 ② よって求められた値を2進数では『2の補数』
という
補数
b進法において、自然数aを表現するのに必要な最小の桁数をnとしたとき、
という
(詳しく書くと...)
補数
1の補数
「足しても桁上がりしない数のうち最大の数」
(ここでいう桁とは演算範囲の桁数を指す )
補数
1の補数
「足しても桁上がりしない数のうち最大の数」
(ここでいう桁とは演算範囲の桁数を指す )
例:0b01110100 → 0b10001011
(0b01110100 + 0b 10001011 = 0b11111111)
補数
1の補数
「足しても桁上がりしない数のうち最大の数」
(ここでいう桁とは演算範囲の桁数を指す )
例:0b01110100 → 0b10001011
(0b01110100 + 0b 10001011 = 0b11111111)
=>各ビットを反転させたもの
補数
2の補数
「桁が一つ繰り上がる数 」
(ここでいう桁とは演算範囲の桁数を指す )
補数
2の補数
「桁が一つ繰り上がる数 」
(ここでいう桁とは演算範囲の桁数を指す )
例:0b01110100 → 0b10001100
(0b01110100 + 0b 10001100 = 0b100000000)
補数
2の補数
「桁が一つ繰り上がる数 」
(ここでいう桁とは演算範囲の桁数を指す )
例:0b01110100 → 0b10001100
(0b01110100 + 0b 10001100 = 0b100000000)
=>各ビットを反転させ、1を足したもの
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
→ビット反転 + 1 (0b11110001 + 1)
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
→ビット反転 + 1 (0b11110001 + 1)
→0b11110010
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
→ビット反転 + 1 (0b11110001 + 1)
→0b11110010
14 + (-14) =
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
→ビット反転 + 1 (0b11110001 + 1)
→0b11110010
14 + (-14) = 0b00001110
+ 0b11110010
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
→ビット反転 + 1 (0b11110001 + 1)
→0b11110010
14 + (-14) = 0b00001110
+ 0b11110010
= 0b100000000
2進数による負の値の表現
コンピュータで2進数の負の値を表すときは絶対値の「2の補数」を使用して表現する
例: -14
|-14|=14 (0b00001110)の2の補数
→ビット反転 + 1 (0b11110001 + 1)
→0b11110010
14 + (-14) = 0b00001110
+ 0b11110010
= 0b100000000
→ 0 ( 演算範囲は8桁だからあふれた9桁目は無視)
ビット演算とは
ビット演算の利点
ビット演算子の種類
NOT (否定) ~x
| x | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 |
|---|---|---|---|---|---|---|---|---|
| ~x | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |
AND (論理積) x&y
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| y | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
| x&y | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
OR (論理和) x|y
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| y | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
| x|y | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 |
XOR (排他的論理和) x ^ y
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| y | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
| x^y | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
右シフト x >> n
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| x>>n | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
{
{
n=2
x : unsigned char
空いた桁は0で埋まる
このbitは捨てられる
(ほぼ)
左シフト x << n
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| x<<n | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
{
{
n=2
x : unsigned char
空いた桁は0で埋まる
このbitは捨てられる
(以下参考)
※注意点
※注意点
シフトには2種類ある
※注意点
シフトには2種類ある
・算術シフト
シフトによって空いたビット部分を符号ビットを同じもので詰める
・論理シフト
シフトによって空いたビット部分を常に0で詰める
※注意点
シフトには2種類ある
・算術シフト
シフトによって空いたビット部分を符号ビットを同じもので詰める
・論理シフト
シフトによって空いたビット部分を常に0で詰める
符号が正のときはどちらも同じ
※注意点
シフトには2種類ある
・算術シフト
シフトによって空いたビット部分を符号ビットを同じもので詰める
・論理シフト
シフトによって空いたビット部分を常に0で詰める
符号が正のときはどちらも同じ
=>問題は符号が負のとき
※注意点
・論理シフト
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| x>>n | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
n=2
x : unsigned char
空いた桁は0で埋まる
{
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| x<<n | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
※注意点
・算術シフト
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| x>>n | 1 | 0 | 0 | 1 | 1 | 1 |
n=2
x : signed char
空いた桁は で埋まる
{
| x | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
| x<<n | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
1
1
1
1
=>常に符号は変わらない
※注意点
これらはコンパイラによって決まる
gccでは
算術シフト signed int/long/long long
論理シフト unsigned int/long/long long
予期せぬ挙動をすることがあるので注意!
| bit に i 番目のフラグが立っているか
(0-indexed) |
if (bit & (1<<i)) |
| bit に i 番目のフラグが消えているか | if (!(bit & (1<<i))) |
| bit に i 番目のフラグを立てる | bit|= (1<<i) |
| bit に i 番目のフラグを消す | bit &= ~(1<<i) |
| bit に何個のフラグが立っているか | __builtin_popcount(bit) (int)
__builtin_popcountl(bit) (long) __builtin_popcountll(bit) (long long) |
| 小さい方から何桁目に初めて立ったフラグが現れるか
(1-indexed) |
__builtin_ffs(bit) (int)
__builtin_ffsl(bit) (long) __builtin_ffsll(bit) (long long) |
フラグ管理
std::bitset を用いた出力
#include <iostream>
#include <bitset>
using namespace std;
int main() {
int A = 0x2d;
int B = 0x19;
cout << bitset<8>(A) << " AND " << bitset<8>(B) << " = " << bitset<8>(A&B) << endl;
}
00101101 AND 00011001 = 00001001
出力
std::bitsetを使うと2進数で出力できる(デバッグでよく使う)
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|
1~8と書かれたボールがあって箱に入れる場合の数
例えば、{A,B,C}という集合があるとする
| 10進数 | 2進数 | 部分集合 |
|---|---|---|
| 0 | 000 | xxx |
| 1 | 001 | xxA |
| 2 | 010 | xBx |
| 3 | 011 | xBA |
| 4 | 100 | Cxx |
| 5 | 101 | CxA |
| 6 | 110 | CBx |
| 7 | 111 | CBA |
1を足していくだけで部分集合の全列挙が可能!
2進数の0,1,2桁にA,B,Cを対応させると...
bitはN個の要素の集合を整数で表すことができるーーー!!!!!!!!
bit 全探索のサンプルコードです
vector<int> vec;
int n = vec.size();
for(int bit=0;bit<((int)1<<n);bit++){
for(int i=0;i<n;i++){
if(((int)1<<i)&bit){
/* 処理を書く */
}
}
/* 処理を書く */
}vector<int> vec;
int n = vec.size();
for(int bit=0;bit<((int)1<<n);bit++){
for(int i=0;i<n;i++){
if(((int)1<<i)&bit){
/* 処理を書く */
}
}
/* 処理を書く */
}"1<<n" は1をnだけ左シフトする
=>つまり、 と同じ!
vector<int> vec;
int n = vec.size();
for(int bit=0;bit<((int)1<<n);bit++){
for(int i=0;i<n;i++){
if(((int)1<<i)&bit){
/* 処理を書く */
}
}
/* 処理を書く */
}"1<< i" はi桁目のビットのみ1
"&" は各ビットについて共に1なら1
"(1<< i)&bit" => bit のi桁目が1なら真(0以外)になる
2変数を同時に保持する方法の一つとしてpairがあるが、他の方法もある
例えば、10×10のxy座標があるとする
z = y × 10 + x
とおくと変数一つで x, y の情報を保持できる
x = z % 10
y = z / 10
とすると復元もできる
二次元情報を一次元に移すことができるので便利!
(戯言)