C++で和とXORが等しくなる整数の個数を求めるアルゴリズム
問題概要
この問題では、整数 n が与えられます。i = 0 から n までの範囲において、加算結果とXOR(排他的論理和)の結果が一致する、すなわち (n + i) = (n ^ i) を満たす整数 i の個数を求めるプログラムを作成します。
入出力例
入力: n = 4
出力: 4
説明:
i = 0 から n までのすべての値を確認すると、次のようになります。
| i | n + i | n ^ i | 一致するか |
|---|---|---|---|
| 0 | 4 + 0 = 4 | 4 ^ 0 = 4 | ○ |
| 1 | 4 + 1 = 5 | 4 ^ 1 = 5 | ○ |
| 2 | 4 + 2 = 6 | 4 ^ 2 = 6 | ○ |
| 3 | 4 + 3 = 7 | 4 ^ 3 = 7 | ○ |
| 4 | 4 + 4 = 8 | 4 ^ 4 = 0 | × |
したがって、条件を満たす整数の個数は 4 となります。
解法アプローチ
最もシンプルな解法は、すべての i について (n + i) と (n ^ i) を実際に計算し、両者を比較して等しくなる場合だけカウントを増やしていく方法です。この方法の計算量は O(n) です。
アルゴリズム
ステップ1: i = 0 から n まですべての値についてループします。
ステップ1.1: (n + i) の値を計算します。
ステップ1.2: (n ^ i) の値を計算します。
ステップ1.3: 手順1.1と1.2で得た値を比較します。
ステップ1.4: 両者が等しければ、カウントを1増やします。
ステップ2: 最終的なカウントを出力します。
解法の動作を示すC++プログラム
実装例
#include <iostream>
using namespace std;
int main() {
int n = 5;
int counter = 0;
for(int i=0; i<=n; i++ )
if ( (n+i) == (n^i) )
counter++;
cout<<"The count of integers with equal sum and XOR is "<<counter;
return 0;
}
出力
The count of integers with equal sum and XOR is 2
より効率的な解法
上記の方法でも正しく答えを求められますが、ビット演算の性質を利用すると、さらに効率よく解くことができます。
n ^ i = n + i が成り立つとき、必ず n & i = 0 となります。
これは、加算において「n + i = (n ^ i) + 2 × (n & i)」という関係が常に成り立つことから説明できます。つまり、繰り上がり(キャリー)が一切発生しない場合にのみ、加算結果とXORの結果が一致するのです。
n & i = 0 となるためには、n と i の間で「セットされたビット」と「セットされていないビット」が互いに反対の位置に配置されている必要があります。言い換えると、n のビットが 0 になっている位置については、i の対応するビットは 0 でも 1 でも自由に選べるため、答えは 2(n のセットされていないビットの個数)乗 になります。
効率的な解法のC++実装例
#include <iostream>
using namespace std;
int countValuesWithEqualSumXOR(int n) {
int countUnSetBits=0;
while (n) {
if ((n & 1) == 0)
countUnSetBits++;
n=n>>1;
}
return 1 << countUnSetBits;
}
int main()
{
int n = 6;
cout<<"The count of integers with equal sum and XOR is "<<countValuesWithEqualSumXOR(n);
return 0;
}
出力
The count of integers with equal sum and XOR is 2
まとめ
全探索では O(n) の計算量が必要ですが、「n & i = 0」という性質を使えば、n のセットされていないビットを数えるだけで、答えを O(log n) で求められます。たとえば n = 6(2進数で 110)の場合、セットされていないビットは 1 個なので、答えは 21 = 2 となり、実際の出力と一致します。
-
C++で配列を等しい和の2つの部分集合に分割できるか判定する方法
問題概要正の整数のみを含む空でない配列が与えられたとき、その配列を要素の合計が等しい2つの部分集合に分割できるかどうかを判定します。例えば、入力が [1, 5, 11, 5] の場合、出力は true になります。これは、この配列を [1, 5, 5] と [11] の2つの部分集合に分けると、どちらも合計が 11 で等しくなるためです。解法のアプローチ(動的計画法)この問題は「部分和問題」の一種であり、動的計画法(DP)を用いて効率的に解くことができます。基本的な考え方は以下の通りです。配列全体の合計が奇数の場合、2つの等しい整数に分割することは不可能なので、即座に false を返します。
-
C++で1〜Nの数の合計がSになる最小個数を求める
問題文1からNまでのN個の整数と、ある整数Sが与えられます。使用できる各数はN以下という制約のもとで、合計がSになるために必要な「数の個数」の最小値を求めて出力してください。例n = 7、s = 10 の場合、必要な数は最小で2個です。たとえば、次のような組み合わせが考えられます。(7, 3) (6, 4)アルゴリズム合計Sをできるだけ少ない個数で作るには、大きな数(最大でN)を優先的に使えばよいことが分かります。したがって、答えは次の式で計算できます。S % N > 0 のとき : (S / N) + 1 S % N == 0 のとき : S / Nつまり、これは「SをNで割った値の切