알고리즘에서 쓰는 비트 연산: 비트마스크 DP, XOR 트릭, 비트 카운팅, 해밍 거리, bitset
원소가 n개인 집합의 부분집합은 n비트 정수 하나로 표현할 수 있습니다. i번 비트가 1이면 i번 원소가 포함된 것입니다. 이렇게 표현하면 부분집합을 std::vector<int>나 std::set<int>로 들고 다닐 때와 달리, 복사는 정수 대입이고, 원소 추가와 포함 검사는 비트 연산 한 번이며, 집합 자체를 배열의 인덱스로 쓸 수 있습니다. 마지막 성질 덕분에 “지금까지 방문한 도시의 집합” 같은 상태를 DP 테이블의 차원으로 쓰는 비트마스크 DP가 가능해집니다.
이 글은 알고리즘 문제와 실무 코드에서 자주 쓰는 비트 연산 패턴(비트마스크, XOR 성질, 비트 개수 세기, 비트마스크 DP, std::bitset, 플래그)과, 부호 있는 정수와 시프트에서 생기는 함정을 다룹니다. 구조화된 바인딩을 쓰는 예제는 C++17, <bit> 헤더를 쓰는 예제는 C++20이 필요합니다.
비트 연산 기본
| 연산자 | 의미 | 예시 |
|---|---|---|
& | 둘 다 1이면 1 | 5 & 3 = 1 |
| | 하나라도 1이면 1 | 5 | 3 = 7 |
^ | 서로 다르면 1 | 5 ^ 3 = 6 |
~ | 모든 비트 반전 | ~0u = 0xFFFFFFFF |
<< | 왼쪽 시프트 | 1u << 3 = 8 |
>> | 오른쪽 시프트 | 8u >> 2 = 2 |
비트마스크 조작
unsigned mask = 0;
unsigned i = 3;
mask |= (1u << i); // i번 비트 켜기
mask &= ~(1u << i); // i번 비트 끄기
mask ^= (1u << i); // i번 비트 뒤집기
bool has = (mask >> i) & 1u; // i번 비트 검사
unsigned low = mask & (0u - mask); // 가장 낮은 1 비트만 남기기 (mask & -mask)
mask &= mask - 1; // 가장 낮은 1 비트 지우기
mask & -mask가 가장 낮은 1 비트를 남기는 이유는 2의 보수에서 -mask가 ~mask + 1이기 때문입니다. ~mask는 가장 낮은 1 비트 아래쪽이 모두 1이고 그 비트는 0인데, 1을 더하면 올림이 그 위치에서 멈춥니다. 결과적으로 그 비트만 원래 값과 겹칩니다. 부호 없는 타입에서 0u - mask로 쓰면 오버플로가 정의된 동작(모듈러 연산)이라 안전합니다.
XOR의 성질
XOR은 교환법칙과 결합법칙이 성립하고, a ^ a = 0, a ^ 0 = a입니다. 따라서 여러 값을 XOR하면 순서와 상관없이, 짝수 번 등장한 값은 모두 상쇄되고 홀수 번 등장한 값만 남습니다.
C++20 <bit> 헤더
직접 구현하던 비트 트릭 상당수가 C++20 <bit>에 표준 함수로 들어왔습니다. 컴파일러가 대상 CPU에 맞는 명령(popcnt, lzcnt, tzcnt 등)으로 바꿔 주고, 부호 있는 타입을 넘기면 컴파일 오류가 나서 아래에서 설명할 부호 관련 실수도 막아 줍니다.
| 하려는 일 | 직접 구현 | C++20 표준 |
|---|---|---|
| 2의 거듭제곱인가 | n != 0 && (n & (n - 1)) == 0 | std::has_single_bit(n) |
| 1의 개수 | 루프 또는 __builtin_popcount | std::popcount(n) |
| 최상위 비트 위치 + 1 | 시프트 루프 | std::bit_width(n) |
| 오른쪽 끝 0의 개수 | __builtin_ctz (0이면 정의되지 않음) | std::countr_zero(n) (0이면 비트 수) |
| 왼쪽 끝 0의 개수 | __builtin_clz (0이면 정의되지 않음) | std::countl_zero(n) |
| n 이상인 가장 작은 2의 거듭제곱 | 비트 스미어링 | std::bit_ceil(n) |
#include <bit>
unsigned n = 40; // 0b101000
std::has_single_bit(n); // false
std::popcount(n); // 2
std::bit_width(n); // 6 (최상위 비트 인덱스는 5)
std::countr_zero(n); // 3
std::bit_ceil(n); // 64
GCC와 Clang의 __builtin_clz, __builtin_ctz는 인자가 0이면 정의되지 않은 동작이고, __builtin_ffs(x)는 가장 낮은 1 비트의 위치에 1을 더한 값(0이면 0)을 반환합니다. 표준 함수는 0에 대해서도 결과가 정의되어 있어 경계 처리가 단순해집니다.
부분집합 열거
#include <iostream>
#include <vector>
void enumerateSubsets(const std::vector<int>& arr) {
const unsigned n = static_cast<unsigned>(arr.size()); // n < 32 가정
for (unsigned mask = 0; mask < (1u << n); ++mask) {
for (unsigned i = 0; i < n; ++i) {
if (mask & (1u << i)) std::cout << arr[i] << ' ';
}
std::cout << '\n';
}
}
// {1, 2, 3} → (빈 줄), 1, 2, 1 2, 3, 1 3, 2 3, 1 2 3
부분집합이 2^n개이므로 이 방법은 n이 20 남짓까지가 현실적인 한계입니다. 2^20은 약 100만, 각 부분집합마다 n번 비트를 검사하면 약 2천만 번의 연산입니다.
특정 집합 mask의 부분집합만 순회할 때는 (sub - 1) & mask로 다음 부분집합을 바로 구할 수 있습니다.
for (unsigned sub = mask; sub != 0; sub = (sub - 1) & mask) {
// sub는 mask 자신부터 시작해 공집합을 제외한 모든 부분집합을 내림차순으로 순회
}
반복 횟수는 2^popcount(mask) - 1입니다. 모든 mask에 대해 이 순회를 하면 전체 반복 횟수가 3^n이 되는데, 각 원소가 “mask에 없음 / mask에만 있음 / sub에도 있음” 세 상태 중 하나이기 때문입니다. 부분집합 위에서 정의되는 DP(집합 분할 등)의 복잡도가 O(3^n)인 이유입니다.
부분집합 합과 중간에서 만나기
원소 n개 중 합이 target인 부분집합이 있는지 비트마스크로 모두 확인하면 O(n·2^n)입니다. n이 40이면 2^40(약 1조)이라 불가능하지만, 배열을 반으로 나눠 각 절반의 부분합을 따로 구하면 2^20 크기 두 개로 줄어듭니다(meet in the middle).
#include <cstdint>
#include <unordered_set>
#include <vector>
bool hasSubsetSum(const std::vector<int>& arr, long long target) {
const unsigned n = static_cast<unsigned>(arr.size());
const unsigned half = n / 2, rest = n - half; // half, rest < 32 가정
std::unordered_set<long long> left_sums;
for (unsigned mask = 0; mask < (1u << half); ++mask) {
long long sum = 0;
for (unsigned i = 0; i < half; ++i)
if (mask & (1u << i)) sum += arr[i];
left_sums.insert(sum);
}
for (unsigned mask = 0; mask < (1u << rest); ++mask) {
long long sum = 0;
for (unsigned i = 0; i < rest; ++i)
if (mask & (1u << i)) sum += arr[half + i];
if (left_sums.count(target - sum)) return true;
}
return false;
}
합은 long long으로 누적했습니다. 원소가 수십 개이고 값이 크면 int 합이 넘칠 수 있기 때문입니다. 존재 여부가 아니라 개수를 세거나 target 이하의 최댓값을 찾는 문제라면, 한쪽 부분합을 정렬해 두고 이진 검색하는 방식을 씁니다.
XOR로 홀수 번 등장한 원소 찾기
다른 모든 원소는 짝수 번, 하나만 홀수 번 등장한다면 전체를 XOR한 결과가 그 원소입니다. O(n) 시간, O(1) 추가 공간입니다.
#include <utility>
#include <vector>
int findSingle(const std::vector<int>& arr) {
int result = 0;
for (int x : arr) result ^= x;
return result;
}
// 두 개가 홀수 번 등장: 둘이 다른 비트 하나를 기준으로 그룹을 나눔
std::pair<int, int> findTwoSingles(const std::vector<int>& arr) {
unsigned xor_all = 0;
for (int x : arr) xor_all ^= static_cast<unsigned>(x); // = a ^ b, a != b이므로 0이 아님
unsigned diff_bit = xor_all & (0u - xor_all); // a와 b가 다른 가장 낮은 비트
unsigned a = 0, b = 0;
for (int x : arr) {
if (static_cast<unsigned>(x) & diff_bit) a ^= static_cast<unsigned>(x);
else b ^= static_cast<unsigned>(x);
}
return {static_cast<int>(a), static_cast<int>(b)};
}
// {1, 2, 1, 2, 3, 4} → {3, 4} 또는 {4, 3}
두 값 a, b를 찾는 경우, 전체 XOR은 a ^ b이고 a와 b가 다르므로 0이 아닙니다. 그 결과에서 1인 비트 하나를 고르면 a와 b는 그 비트에서 값이 다르므로 서로 다른 그룹으로 갈라지고, 나머지 짝수 번 등장한 값들은 같은 값끼리 같은 그룹에 들어가 상쇄됩니다. int로 xor_all & -xor_all을 계산하면 xor_all이 INT_MIN일 때 부호 반전이 오버플로되어 정의되지 않은 동작이 되므로, 위처럼 부호 없는 타입으로 계산합니다.
비트 개수 세기와 해밍 거리
두 정수의 해밍 거리(비트가 다른 위치의 개수)는 XOR로 다른 비트만 1로 만든 뒤 1의 개수를 세면 됩니다.
#include <bit>
#include <cstdint>
int hammingDistance(std::uint32_t a, std::uint32_t b) {
return std::popcount(a ^ b); // C++20
}
// C++20 이전에 이식 가능하게: 1의 개수만큼만 반복 (Brian Kernighan)
int popcountKernighan(std::uint32_t x) {
int count = 0;
while (x) {
x &= x - 1; // 가장 낮은 1 비트 제거
++count;
}
return count;
}
__builtin_popcount는 unsigned int를 받으므로 int -1을 넘기면 암시적으로 변환되어 32가 나옵니다. 실제로 자주 나는 실수는 64비트 값을 __builtin_popcount에 넘기는 것입니다. 상위 32비트가 잘려 나가 개수가 틀리므로, 64비트에는 __builtin_popcountll을 써야 합니다. std::popcount는 인자 타입에 맞춰 동작하므로 이 실수가 생기지 않습니다. MSVC에는 __builtin_popcount가 없고 __popcnt 내장 함수가 있지만, 이 명령을 지원하지 않는 CPU에서 실행하면 잘못된 명령 예외가 나므로 C++20이라면 std::popcount가 가장 간단합니다.
비트마스크 DP: 외판원 문제
n개 도시를 모두 한 번씩 방문하고 출발지로 돌아오는 최소 비용을 구하는 문제를 모든 순열로 풀면 O(n!)입니다. “지금까지 방문한 도시의 집합”과 “현재 위치”만 알면 앞으로의 최적 경로가 결정된다는 점을 이용하면, 상태를 dp[mask][cur]로 두고 O(2^n · n²)에 풀 수 있습니다. n = 16이면 2^16 · 256 ≈ 1,700만 번 정도의 전이입니다.
#include <algorithm>
#include <climits>
#include <vector>
constexpr int INF = INT_MAX / 2; // 두 값을 더해도 넘치지 않도록
int tsp(const std::vector<std::vector<int>>& dist) { // 길이 없으면 INF
const int n = static_cast<int>(dist.size());
const unsigned full = (1u << n) - 1;
std::vector<std::vector<int>> dp(1u << n, std::vector<int>(n, INF));
dp[1][0] = 0; // 0번 도시에서 출발, 방문 집합 {0}
for (unsigned mask = 1; mask <= full; ++mask) {
for (int cur = 0; cur < n; ++cur) {
if (!(mask & (1u << cur)) || dp[mask][cur] == INF) continue;
for (int next = 0; next < n; ++next) {
if (mask & (1u << next) || dist[cur][next] == INF) continue;
unsigned nmask = mask | (1u << next);
dp[nmask][next] = std::min(dp[nmask][next], dp[mask][cur] + dist[cur][next]);
}
}
}
int ans = INF;
for (int cur = 1; cur < n; ++cur) {
if (dp[full][cur] != INF && dist[cur][0] != INF)
ans = std::min(ans, dp[full][cur] + dist[cur][0]);
}
return ans;
}
mask를 0부터 증가하는 순서로 처리하면 되는 이유는 전이가 항상 비트를 하나 켜서 더 큰 mask로 가기 때문입니다. INF를 INT_MAX로 두면 dp + dist에서 오버플로가 나므로 절반 정도로 잡고, 길이 없는 간선도 같은 INF로 표시해 건너뜁니다. 시작 상태 dp[1][0] = 0을 빠뜨리면 모든 값이 INF로 남습니다.
메모리는 2^n × n개의 int라서 n = 20이면 약 2,000만 개, 80MB 정도입니다. 출발 도시는 항상 방문 집합에 포함되므로 0번 비트를 상태에서 빼면 절반으로 줄일 수 있습니다. 이런 큰 테이블을 std::array로 지역 변수에 두면 스택이 넘치므로 std::vector처럼 힙에 둡니다.
std::bitset
비트 수가 컴파일 시간에 정해지고 64를 넘으면 std::bitset<N>이 편합니다. 내부적으로 워드 배열이라서 &, |, ^, count() 같은 연산이 워드 단위로 처리되고, 비트 하나당 1비트만 차지합니다.
#include <bitset>
#include <string>
void bitset_example() {
std::bitset<8> b1(0b10101010);
std::bitset<8> b2("11110000");
auto both = b1 & b2; // 10100000
b1.set(0); // 0번 비트 켜기
b1.reset(1); // 1번 비트 끄기
b1.flip(2); // 2번 비트 뒤집기
bool bit3 = b1.test(3); // 범위 밖이면 std::out_of_range 예외
bool bit4 = b1[4]; // 범위 검사 없음
std::size_t ones = b1.count();
bool any = b1.any(), none = b1.none(), all = b1.all();
std::string s = b1.to_string(); // 최상위 비트가 왼쪽
}
변환에는 몇 가지 함정이 있습니다. 문자열 생성자는 왼쪽 글자를 최상위 비트로 읽으므로 bitset<8>{"10101010"}의 [0]은 맨 오른쪽 글자인 0입니다. 문자열이 N보다 길면 앞쪽 N글자만 쓰고 나머지는 예외 없이 버리며(bitset<4>{"110011"}은 1100), '0'과 '1'이 아닌 글자가 있으면 std::invalid_argument를 던지므로 외부 입력은 길이부터 검사하는 편이 좋습니다. 정수로 꺼내는 to_ulong()은 값이 unsigned long에 들어가지 않으면 std::overflow_error를 던지는데, Windows는 64비트에서도 unsigned long이 32비트라서 Linux에서 잘 돌던 코드가 상위 비트가 켜진 데이터를 만나는 순간에야 실패합니다. 64비트까지는 to_ullong()을 쓰고, 그보다 크거나 파일·네트워크로 내보낼 때는 내부 워드 배치가 구현마다 다르므로 memcpy 대신 to_string()이나 워드 단위로 잘라 직렬화합니다.
에라토스테네스의 체처럼 큰 불리언 배열이 필요할 때도 쓸 수 있습니다.
#include <bitset>
#include <cassert>
#include <vector>
constexpr int kMaxN = 1'000'000;
std::vector<int> sievePrimes(int n) {
assert(n <= kMaxN);
static std::bitset<kMaxN + 1> is_prime; // 약 125KB: 스택 대신 정적 저장소
is_prime.set();
is_prime[0] = is_prime[1] = false;
for (long long i = 2; i * i <= n; ++i) {
if (!is_prime[i]) continue;
for (long long j = i * i; j <= n; j += i) is_prime[j] = false;
}
std::vector<int> primes;
for (int i = 2; i <= n; ++i)
if (is_prime[i]) primes.push_back(i);
return primes;
}
operator[]는 범위를 검사하지 않으므로 n이 크기를 넘지 않는지 따로 확인해야 합니다. 크기가 실행 중에 정해진다면 std::vector<bool>(비트 단위로 압축되는 특수화)이나 Boost의 dynamic_bitset을 씁니다. 비트 수가 64 이하라면 uint64_t 하나로 직접 다루는 것이 가장 단순하고 빠릅니다.
플래그와 비트 패킹
권한 플래그
#include <cstdint>
#include <type_traits>
enum class Permission : std::uint8_t {
None = 0,
Read = 1u << 0,
Write = 1u << 1,
Execute = 1u << 2,
};
template <typename E>
class BitFlags {
using U = std::underlying_type_t<E>;
U value_ = 0;
public:
constexpr BitFlags() = default;
constexpr BitFlags(E e) : value_(static_cast<U>(e)) {}
constexpr bool test(E e) const { return (value_ & static_cast<U>(e)) != 0; }
constexpr void set(E e) { value_ |= static_cast<U>(e); }
constexpr void reset(E e) { value_ &= static_cast<U>(~static_cast<U>(e)); }
constexpr U raw() const { return value_; }
};
void example() {
BitFlags<Permission> user(Permission::Read);
user.set(Permission::Write);
bool can_write = user.test(Permission::Write); // true
user.reset(Permission::Read);
}
enum class는 정수로 암시적 변환되지 않아서 |나 &를 바로 쓸 수 없습니다. 위처럼 작은 래퍼를 두거나, enum에 대해 연산자를 오버로드하면 다른 종류의 플래그를 실수로 섞는 것을 컴파일러가 막아 줍니다. uint8_t에 ~를 적용하면 정수 승격으로 int가 되므로, reset에서 다시 원래 타입으로 변환했습니다.
운영체제에서 가장 흔히 보는 플래그 집합은 Unix 파일 권한입니다. 9개의 비트를 소유자·그룹·기타 세 묶음으로 3비트씩 나눈 비트마스크라서 8진수 한 자리가 한 묶음과 정확히 맞고, 0755는 rwxr-xr-x가 됩니다. <sys/stat.h>의 S_IRUSR, S_IWGRP 같은 상수가 이 비트들이며, 그룹 쓰기 권한만 빼는 것은 mode & ~S_IWGRP, 셸의 umask 022가 새 파일에서 그룹·기타의 쓰기 비트를 지우는 것도 같은 패턴입니다.
비트 필드(unsigned read : 1;)도 선언이 읽기 쉽지만, 비트가 어느 쪽부터 배치되는지와 패딩이 구현 정의입니다. 같은 구조체를 파일에 저장하거나 네트워크로 보내면 컴파일러나 플랫폼이 바뀔 때 값이 뒤섞일 수 있습니다. 비트 필드는 프로세스 안에서만 쓰는 상태에 한정하고, 파일 헤더나 프로토콜 필드처럼 외부로 나가는 형식은 위처럼 명시적인 비트마스크 상수로 정의하는 것이 안전합니다.
작은 값 여러 개를 하나의 정수에 담기
#include <cstdint>
// 0~15 값 4개를 16비트에 4비트씩
std::uint16_t pack4(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) {
return static_cast<std::uint16_t>((a & 0xF) | ((b & 0xF) << 4) | ((c & 0xF) << 8) | ((d & 0xF) << 12));
}
void unpack4(std::uint16_t p, std::uint8_t& a, std::uint8_t& b, std::uint8_t& c, std::uint8_t& d) {
a = p & 0xF;
b = (p >> 4) & 0xF;
c = (p >> 8) & 0xF;
d = (p >> 12) & 0xF;
}
넣을 때 & 0xF로 범위를 잘라 두지 않으면, 15보다 큰 값이 들어왔을 때 옆 필드를 덮어씁니다.
바이트 순서
네트워크 프로토콜처럼 바이트 순서가 정해진 데이터를 읽을 때는, 메모리를 정수로 재해석하지 않고 바이트를 하나씩 읽어 시프트로 조립하면 호스트의 엔디언과 관계없이 올바른 값이 나옵니다.
#include <cstdint>
std::uint32_t load_be32(const unsigned char* p) {
return (std::uint32_t{p[0]} << 24) | (std::uint32_t{p[1]} << 16) |
(std::uint32_t{p[2]} << 8) | std::uint32_t{p[3]};
}
C++20의 std::endian::native로 호스트 엔디언을 확인할 수 있고, C++23에는 바이트를 뒤집는 std::byteswap이 추가되었습니다. 컴파일러는 위와 같은 바이트 조립 코드를 대개 한 번의 load와 바이트 교환 명령으로 최적화합니다.
부호와 시프트의 함정
부호 있는 정수의 시프트
비트 연산에는 부호 없는 타입을 쓰는 것이 원칙입니다. 부호 있는 정수의 시프트 규칙은 표준 버전마다 달랐기 때문입니다.
int b = 1 << 31; // 32비트 int: C++20부터 INT_MIN으로 정의, 그 이전은 구현 정의 또는 미정의
int x = -8 >> 1; // C++20부터 -4(산술 시프트)로 정의, 그 이전은 구현 정의
unsigned u = 1u << 31; // 항상 2147483648
unsigned v = 0xFFFFFFF8u >> 1; // 항상 0x7FFFFFFC (논리 시프트)
C++20은 부호 있는 정수를 2의 보수로 못박으면서 왼쪽 시프트를 “결과를 2^N으로 나눈 나머지와 합동인 값”으로, 오른쪽 시프트를 산술 시프트로 정의했습니다. 그 이전 표준에서는 부호 비트로 넘치는 왼쪽 시프트와 음수의 왼쪽 시프트가 미정의 또는 구현 정의였고, 음수의 오른쪽 시프트는 구현 정의였습니다(주요 컴파일러는 모두 산술 시프트). C++20에서도 결과가 음수라는 사실은 남으므로, int mask = 1 << 31; 뒤의 mask > 0 같은 비교는 여전히 의도와 다르게 동작합니다.
시프트 양이 비트 수 이상
어느 표준 버전에서든 시프트 양이 피연산자의 비트 수 이상이거나 음수이면 정의되지 않은 동작입니다. x86에서 32비트 시프트 명령은 시프트 양의 하위 5비트만 쓰기 때문에 1u << 32가 1이 되는 것처럼 보이기도 하지만, 컴파일러가 상수로 계산하면 다른 결과가 나올 수 있습니다.
unsigned n = 32;
for (unsigned mask = 0; mask < (1u << n); ++mask) {} // 정의되지 않은 동작
// n이 32 이상일 수 있다면 64비트로
std::uint64_t limit = 1ULL << n; // n < 64 이어야 함
for (std::uint64_t mask = 0; mask < limit; ++mask) {}
1 << i처럼 리터럴 1은 int라서 i가 31이면 부호 비트로, 32 이상이면 범위를 넘습니다. 비트마스크 DP에서 n이 커질 수 있다면 처음부터 1u << i나 1ULL << i로 쓰는 습관이 이런 버그를 막습니다.
연산자 우선순위
==, !=, <는 &, ^, |보다 우선순위가 높습니다.
if (mask & 1 == 0) {} // mask & (1 == 0) → mask & 0 → 항상 false
if ((mask & 1) == 0) {} // 의도한 짝수 검사
시프트도 덧셈보다 우선순위가 낮아서 1 << n - 1은 1 << (n - 1)입니다. 비트 연산이 섞인 식은 우선순위를 외우기보다 괄호로 의도를 드러내는 편이 안전합니다. GCC와 Clang은 -Wparentheses(-Wall에 포함)로 이런 식에 경고를 냅니다.
곱셈을 시프트로 바꿀 필요는 없다
x * 8을 x << 3으로 바꾸는 최적화는 컴파일러가 이미 합니다. -O2로 빌드하면 둘은 같은 명령이 됩니다. 부호 있는 정수의 x / 2와 x >> 1은 음수에서 반올림 방향이 달라 결과 자체가 다르므로(-3 / 2는 -1, -3 >> 1은 -2), 나눗셈이 의도라면 /를 써야 합니다. 시프트는 비트 위치를 옮긴다는 의미일 때 쓰는 것이 읽는 사람에게도 정확합니다.
참고 자료
- cppreference: Bit manipulation (
) - cppreference: std::bitset
- cppreference: Arithmetic operators (shift)
- Henry S. Warren, 《Hacker’s Delight》
같이 보면 좋은 글
- 탐욕 알고리즘이 맞는지 증명하기: 교환 논증과 활동 선택·거스름돈·작업 스케줄링 예제
- C++ STL 알고리즘 기초: sort·find·transform·accumulate 실전 활용
- C++ 메모리 풀 구현 비교: 객체 풀, 슬랩 할당자, 아레나, std::pmr