출처: https://cses.fi/problemset/1158


문제 설명

You are in a book shop which sells n different books. You know the price and number of pages of each book.

You have decided that the total price of your purchases will be at most x. What is the maximum number of pages you can buy? You can buy each book at most once.

입력 

N X // Number of books, maximum total price

h_i // books price

s_i // number of pages

 

출력

the maximum number of pages.

입력 예 

4 10
4 8 5 3
5 12 8 1

출력 예

13

제약조건

  • 1<= N <= 1000
  • 1<= X <= 1e5
  • 1<= h_i, s_i <= 1000 

문제 풀이 내용

Knapsack 문제이다. 어떤 특정한 책을 선택했을 때 페이지와, 그 책을 고르지 않았을 경우에 얻을 수 있는 페이지의 최대값을 비교해서 큰 값을 선택하도록 한다.

프로그램 내용

더보기
...
    vector<int> pr(n,0);
    vector<int> pg(n,0);
    vector<int> dp(x+1,0);
...
    for (int i = 0; i < n; i++) 
        for (int j = x; j >= 1; j--) 
            if (pr[i] <= j) 
                dp[j] = max(dp[j], dp[j - pr[i]] + pg[i]); 
...

 

Dynamic Programming link )

'CSES' 카테고리의 다른 글

CSES 7. Stair Game(1099)  (0) 2020.01.01
CSES 3. Array Description (1746)  (0) 2019.12.31
CSES 3. Grid Paths (1638)  (0) 2019.12.28
CSES 2. Sliding Cost (1077)  (0) 2019.12.28
CSES 2. Sliding Median (1076)  (0) 2019.12.25

출처: https://cses.fi/problemset/task/1638


문제 설명

Consider an N x N grid whose squares may have traps. It is not allowed to move to a square with a trap.

Your task is to calculate the number of paths from the upper-left square to the lower-right square where you only can move right or down.

입력  

First line: N /// grid size

N line : grid information ( . : empty cell, * : trap)

출력

Print the number of paths modulo 1e9 + 7

입력 예

4
....
.*..
...*
*...

출력 예

3

제약조건

1 <= N <= 1000


문제 풀이 내용

어떤 특정한 cell ( path[i][j] ) 까지 도착하는 경로는 왼쪽에서 온 경우( path[i-1][j])와 위쪽에서 온 경우(path[i][j-1])의 합으로 결정된다. 제일 처음 출발하는 cell부터 목표로 하는 cell 까지 cell이 trap(*)인 경우는 제외하고 더해서 만들어 간다. 

프로그램 내용

더보기
...
    vector <string> maze(n+1);
    for (int i = 1; i <= n; i++)
        string tmp;
        maze[i] = "."+tmp;

    vector <vector<int>> path(n+1, vector<int>(n+1));

    path[1][0] = 1;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (maze[i][j] == '.')
                path[i][j] = (path[i-1][j]+path[i][j-1]) % M;
...

 

Dynamic Programming link )

'CSES' 카테고리의 다른 글

CSES 3. Array Description (1746)  (0) 2019.12.31
CSES 3. Book Shop (1158)  (0) 2019.12.28
CSES 2. Sliding Cost (1077)  (0) 2019.12.28
CSES 2. Sliding Median (1076)  (0) 2019.12.25
CSES 3. Removing Digits (1637)  (0) 2019.10.14

출처: https://cses.fi/problemset/task/1077


문제 설명

You are given an array of n integers. Your task is to calculate for each window of k elements, from left to right, the minimum total cost of making all elements equal.

You can increase or decrease each element with cost x where x is the difference between the new and the original value. The total cost is the sum of such costs.

입력 

The first input line contains two integers n and k: the number of elements and the size of the window.

Then there are n integers x_1,x_2,,x_n: the contents of the array.

출력  

Output nk+1 values: the costs.

입력 예

8 3
2 4 3 5 8 1 2 1

출력 예

2 2 5 7 7 1

제약조건

  • 1 <= <= n <= 2x10^5
  • 1 <= x_i <= 10^9

문제 풀이 내용

중간값을 출력하는 문제의 확장이다. 다만, 이경우에 윈도우를 움직일때마다 새롭게 변경되는 중간값과 그것을 기준으로 하는 비용을 다시 계산하면, 시간제한에 걸린다. 

바로 직전의 중간값과 비용을 저장하고 있다가, 새로 변경되는 부분만큼만 비용을 갱신하는 방법을 사용한다.

프로그램 내용

더보기
...
    vector<long> num_arr(tN + 1);

    for (int id = 1; id <= tN; ++id)
        cin >> num_arr[id];

	long cost = 0;
    long old_med = 0;
    long med = 0;

    if (tK == 1) 
        for (int id = 1; id <= tN; ++id)
            cout << 0 << " ";
    else
        priority_queue<pair<int, int>, vector<pair<int, int>>, less<pair<int, int>>> lpq;
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> rpq;

        long mid = (tK + 1) / 2;
        int w_size = 0;
        for (int id = 1; id <= tN ; ++id) 
            while (!lpq.empty() && lpq.top().second <= id - tK) lpq.pop();
            while (!rpq.empty() && rpq.top().second <= id - tK) rpq.pop();

            if (w_size < mid) 
                rpq.push(make_pair(num_arr[id], id));
                auto tmp = rpq.top();
                rpq.pop();
                lpq.push(tmp);
                ++w_size;
            else 
                lpq.push(make_pair(num_arr[id], id));
                auto tmp = lpq.top();
                lpq.pop();
                rpq.push(tmp);

            while (!lpq.empty() && lpq.top().second <= id - tK) lpq.pop();
            while (!rpq.empty() && rpq.top().second <= id - tK) rpq.pop();

            if (id == tK) 
                old_med = lpq.top().first;
                for (int idn = 1; idn <= tK; ++idn) 
                    cost += abs(num_arr[idn] - old_med);
            else if (id > tK)  
                med = lpq.top().first;

                cost = cost + abs(med - num_arr[id]) - abs(old_med - num_arr[id - tK]);

                if (tK % 2 == 0) cost -= (med - old_med);

                old_med = med;
            else 
                continue;
            if (num_arr[id - tK + 1] <= lpq.top().first) --w_size;
...

 

Sorting and Searching link )

'CSES' 카테고리의 다른 글

CSES 3. Book Shop (1158)  (0) 2019.12.28
CSES 3. Grid Paths (1638)  (0) 2019.12.28
CSES 2. Sliding Median (1076)  (0) 2019.12.25
CSES 3. Removing Digits (1637)  (0) 2019.10.14
CSES 3. Coin Combinations II (1636)  (0) 2019.10.10

출처: https://cses.fi/problemset/task/1076


문제 설명

You are given an array of n integers. Your task is to calculate the median of each window of k elements, from left to right.

The median is the middle element when the elements are sorted. If the number of elements is even, there are two possible medians and we assume that the median is the smaller of them.

입력 

The first input line contains two integers n and k: the number of elements and the size of the window.

Then there are n integers x_1,x_2,,x_n : the contents of the array.

출력 

Print nk+1 values: the medians.

입력 예

8 3
2 4 3 5 8 1 2 1

출력 예

3 4 5 5 2 1

제약조건

  • 1 <= k <= n <= 2x10^5
  • 1 <= x_i <= 10^9

문제 풀이 내용

입력 배열을 읽어 들인다.

- 윈도우 크기가 1인 경우는 배열을 순서대로 출력한다.

- 윈도우의 크기가 배열 크기와 같은 경우에는 정렬하여 중간값을 출력한다.

 

처음에 작성한 알고리즘 - 같은 값이 여러개 있고, 그것들이 중간값인 경우에 잘못된 값을 출력한다.

배열의 앞에서부터 윈도우 크기만큼 multiset 에 넣고, 첫번째 중간값을 출력한다. 새로운 입력값(a), 현재 중간값(b), 삭제할 값(c)을 비교해서, 새로운 중간값을 출력한다.

  • 삭제할 값이 중간값과 같은 경우, 새로운 값의 크기에 따라 중간값 위치를 움직인다
    • a = b, c > b -> b* = b+1, c < b -> b* = b-1
  • 새로운 값과 삭제할 값이 다른 쪽에 위치하면 중간 값을 움직인다. 
    • a < b && c >= b -> b* = b+1,  a >= b && c < b  -> b* = b-1.

변경한 알고리즘 - median을 중심으로 커지는 것과 작아지는 2개의 priority_queue 2개를 사용한다.

입력을 median을 기준으로 작은쪽에 넣고, 양쪽 크기를 비교해서 같도록 값을 옮겨준다. 커지는 queue의 값이 median이 된다.

프로그램 내용

 

같은 값이 여러개 있고, 그것들이 중간값인 경우에 에러가 나오던 소스

더보기
...
    vector <long> num_arr(nNum);

    multiset <long> sub_arr;

    for(int idx=0; idx < nNum; ++idx)
        cin >> num_arr[idx];

    if (wSize == 1)
        for (int idx = 0; idx < nNum; ++idx) 
            cout << num_arr[idx] << " ";
        cout << endl;

        return 0;
    else if (wSize == nNum)
        sort(num_arr.begin(), num_arr.end()); 
        cout << num_arr[(nNum+1) / 2-1] << endl; 
        return 0;
    else
        for (int idx = 0; idx < wSize ; ++idx) 
            sub_arr.insert(num_arr[idx]);

        auto med = sub_arr.begin();

        /// first median
		for (int idx = 0; idx < (wSize-1)/2 ; ++idx)
            ++med;
        cout << *med << " ";

        /// moving window
        for (int idx = wSize, id=0; idx < nNum; ++idx, ++id) 
            sub_arr.insert(num_arr[idx]); 
            
            if (sub_arr.find(num_arr[id]) == med) 
                if ( num_arr[idx] < *med) 
                    --med; 
                else if ( num_arr[idx] >= *med) 
                    ++med; 
            else 
                if (num_arr[id] < *med && num_arr[idx] >= *med) 
                    ++med; 
                else if (num_arr[id] >= *med && num_arr[idx] < *med) 
                    --med; 
           
            sub_arr.erase(sub_arr.find(num_arr[id])); 
            cout << *med << " "; 
...

오름차순, 내림차순으로 priority queue 2개 만들어서 새로운 값들을 넣으면서 중간값을 출력하는 소스

더보기
...
    vector<long> num_arr(tN+1);

    priority_queue<pair<int, int>, vector<pair<int, int>>, less<pair<int, int>>> lpq;
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> rpq;

    long mid = (tK+1)/2 ;
    int w_size = 0;
    for(int id=1; id <= tN;++id)
        cin >> num_arr[id];

		while (!lpq.empty() && lpq.top().second <= id - tK) lpq.pop();
		while (!rpq.empty() && rpq.top().second <= id - tK) rpq.pop();

		if (w_size < mid)
			rpq.push(make_pair(num_arr[id], id));
			auto tmp = rpq.top();
			rpq.pop();
			lpq.push(tmp);
			++w_size;
		else
			lpq.push(make_pair(num_arr[id], id));
			auto tmp = lpq.top();
			lpq.pop();
			rpq.push(tmp);

		while (!lpq.empty() && lpq.top().second <= id - tK) lpq.pop();
		while (!rpq.empty() && rpq.top().second <= id - tK) rpq.pop();

...

 

Sorting and Searching link )

'CSES' 카테고리의 다른 글

CSES 3. Grid Paths (1638)  (0) 2019.12.28
CSES 2. Sliding Cost (1077)  (0) 2019.12.28
CSES 3. Removing Digits (1637)  (0) 2019.10.14
CSES 3. Coin Combinations II (1636)  (0) 2019.10.10
CSES 3. Coin Combinations I (1635)  (0) 2019.10.09

출처: https://cses.fi/problemset/task/1637


문제 설명

You are given an integer n. On each step, you may subtract from it any one-digit number that appears in it.

How many steps are required to make the number equal to 0?

입력 

The only input line has an integer n.

출력

Print one integer: the minimum number of steps.

입력 예

27

출력 예

5

Explanation: An optimal solution is  27 , 20(-7), 18(-2), 10(-8), 9(-1) - 0 (-9)

제약조건

  • 1 <= n <= 10^6

문제 풀이

어떤 수의 자리수 중에서 가장 큰 숫자를 찾아서, 그 숫자들을 계속 빼주는 방법이다. 

최적의 경우의 수는 각 자리의 수를 빼서 나온 수의 경우의 수 +1 중에서 가장 작은 것이 된다. 

 

입력 N 의 크기를 생각해보면, 기록할 필요 없이 가장 큰 자리수를 찾아서 빼내고, 다시 재귀적으로 돌려도 충분할 것 같다.

프로그램 내용

더보기
...
    vector<int> DP(nNum+1, 1e9);

    DP[0] = 0;
    for (int idx=1; idx <= nNum; ++idx)
        int tmp = idx;
        while(tmp)
            int r_digit = tmp % 10;
            DP[idx] = min( DP[idx], DP[idx-r_digit] + 1);
            tmp /= 10;
...

 

Dynamic Programming link )

'CSES' 카테고리의 다른 글

CSES 2. Sliding Cost (1077)  (0) 2019.12.28
CSES 2. Sliding Median (1076)  (0) 2019.12.25
CSES 3. Coin Combinations II (1636)  (0) 2019.10.10
CSES 3. Coin Combinations I (1635)  (0) 2019.10.09
CSES 3. Minimizing Coins (1634)  (0) 2019.10.09

출처: https://cses.fi/problemset/task/1636


문제 설명

Consider a money system consisting of n coins. Each coin has a positive integer value. Your task is to calculate the number of distinct ordered ways you can produce a money sum xx using the available coins.

For example, if the coins are {2,3,5} and the desired sum is 9, there are 3 ways:

  • 2+2+
  • 3+3+
  • 2+2+2+3

입력 

The first input line has two integers n and x: the number of coins and the desired sum of money.

The second line has n distinct integers c_1,c_2,,c_n: the value of each coin.

출력

Print one integer: the number of ways modulo 10^9+

입력 예

3 9
2 3 5

출력 예

3

제약조건

  • 1 <= <= 100
  • 1 <= x <= 10^
  • 1 <= c_i <= 10^6

문제 풀이

특정한 값(a)을 만드는 경우의 수를 dp(a) 라고 하고, 특정한 코인의 값을 coin(b)라고 하면, 코인 coin(b)를 추가해서 a값을 완성하는 경우와 사용하지 않고 완성하는 경우의 합으로 계산할 수 있다. 

dp(a, b) = dp(a, 0) + dp(a - coin(b)) 

 

0 값을 만드는 방법은 아무것도 고르지 않는 empty set의 한가지 경우 밖에 없으므로, dp(0) = 1이 된다. 

또, dp(coin(a)) = 1, 특정 코인 1개로 표현할 수 있는 방법 1에서 다른 경우들을 완성해서 목표로 하는 값을 찾는 문제이다. 

프로그램 내용

더보기
...
    vector<int> coins(nCoin,0);
    vector<long> dp(tValue+1, 0);

    for (int idx=0; idx < nCoin; ++idx)
        cin >> coins[idx];

    sort(coins.begin(), coins.end());

    dp[0] = 1;

    for (int idx = 0; idx < nCoin; ++idx)
        for (int id = coins[idx]; id <= tValue; ++id)
            dp[id] = (dp[id] + dp[id - coins[idx]]) % 1000000007;
...

 

Dynamic Programming link )

'CSES' 카테고리의 다른 글

CSES 2. Sliding Median (1076)  (0) 2019.12.25
CSES 3. Removing Digits (1637)  (0) 2019.10.14
CSES 3. Coin Combinations I (1635)  (0) 2019.10.09
CSES 3. Minimizing Coins (1634)  (0) 2019.10.09
CSES 3. Dynamic Programming  (0) 2019.10.09

출처: https://cses.fi/problemset/task/1635


문제 설명

Consider a money system consisting of n coins. Each coin has a positive integer value. Your task is to calculate the number of distinct ways you can produce a money sum x using the available coins.

For example, if the coins are {2,3,5} and the desired sum is 9, there are 8 ways: 

  • 2+2+
  • 2+5+
  • 5+2+
  • 3+3+
  • 2+2+2+
  • 2+2+3+
  • 2+3+2+
  • 3+2+2+2

입력 

The first input line has two integers n and x: the number of coins and the desired sum of money.

The second line has n distinct integers c_1,c_2,,c_n : the value of each coin.

출력  

Print one integer: the number of ways modulo 10^9+7

입력 예

3 9
2 3 5

출력 예

8

제약조건

  • 1 <= n <= 100 
  • 1 <= x <= 10^
  • 1 <= c_i <= 10^6

문제 풀이 

특정한 값을 만드는 경우의 수는 코인의 값을 포함하는 경우와 포함하지 않는 경우를 더하면 된다.

먼저, 초기 값으로 목표값이 0인 경우는 아무런 코인도 고르지 않는 empty set 한가지가 된다. (dp[0] = 1)  새로운 코인 값과 기존의 값이 목표값 보다 작은 모든 경우들을 합하면 목표값을 만드는 경우의 수가 된다.

 

목표값이 xValue가 될때까지 dp[]를 완성하면, dp[xValue] 이 원하는 경우의 수가 된다.

프로그램 내용

더보기
...
    vector<int> coins(nCoin);
    vector<long long> dp(xValue + 1, 0);

    for (int idx = 0; idx < nCoin; ++idx)
        cin >> coins[idx];

    sort(coins.begin(), coins.end());

    dp[0] = 1;        /// target value == 0, empty set , 1 way

    for (int idx = 0; idx <= xValue; ++idx)
        for (int id =0; id < nCoin; ++id)
            if (idx + coins[id] <= xValue)
                dp[idx + coins[id]] += dp[idx];
                dp[idx + coins[id]] %= 1000000007;
...

 

Dynamic Programming link )

'CSES' 카테고리의 다른 글

CSES 3. Removing Digits (1637)  (0) 2019.10.14
CSES 3. Coin Combinations II (1636)  (0) 2019.10.10
CSES 3. Minimizing Coins (1634)  (0) 2019.10.09
CSES 3. Dynamic Programming  (0) 2019.10.09
CSES 2. Array Division (1085)  (0) 2019.10.09

출처: https://cses.fi/problemset/task/1634


문제 설명

Consider a money system consisting of n coins. Each coin has a positive integer value. Your task is to produce a sum of money x using the available coins in such a way that the number of coins is minimal.

For example, if the coins are {1,5,7and the desired sum is 11, an optimal solution is 5+5+1 which requires 3 coins.

입력 

The first input line has two integers n and x: the number of coins and the desired sum of money.

The second line has n distinct integers c_1,c_2,,c_n: the value of each coin.

출력  

Print one integer: the minimum number of coins. If it is not possible to produce the desired sum, print -1.

입력 예

3 11
1 5 7

출력 예

3

제약조건

  • 1 <= n <= 100
  • 1 <= x <= 10^6
  • 1 <= c_i <= 10^6

문제 풀이 

코인의 종류와 목표값을 입력 받고, 코인들의 값을 입력 받는다. 목표값이 0인 경우 최소 코인은 0개가 되고, 목표값이 특정한 코인의 값과 같은 경우에 필요한 코인 최소 개수는 1개가 된다.  목표값을 1 부터 x 까지 이동해가면서 코인들을 포함 시키는 경우들에 대해서 검사해서 값을 변경한다. dp[x] 가 원하는 값이 된다. 

(dp[target], coin[j]) check dp[target-coin[j]] >0 

  • dp[target] == initial value(-1) -> dp[target] = dp[target-coin[j]] + 1 
  • dp[target] != initial value -> compare min(dp[target], dp[target-coin[j]] +1)

프로그램 내용

더보기
...
    vector <int> coins(N);
    vector <long> dp(MAX_T+1, -1);

    for(int idx = 0; idx < N ; ++idx)
        cin >> coins[idx];
        dp[coins[idx]] = 1; // target == coin_value, minimum coin 1

    sort(coins.begin(), coins.end()); /// coin value low to high

    dp[0] = 0;  /// target = 0, no coin

    /// Compute minimum coins required for all values from 1 to target
    for (int i=1; i<=target; i++)
        /// Go through all coins smaller than i
        for (int j=0; j < N; j++)
            if (i <= coins[j] ) continue;           // skip current coin

            if ( dp[i - coins[j]] >= 0)             // check current coin use or not
                if (dp[i] == -1)                    // not updated target value
                    dp[i] = 1 + dp[i-coins[j]];
                else                                // add current coin to meet the target value
                    dp[i] = min(dp[i], 1 + dp[i-coins[j]]);
...

 

Dynamic Programming link )

'CSES' 카테고리의 다른 글

CSES 3. Coin Combinations II (1636)  (0) 2019.10.10
CSES 3. Coin Combinations I (1635)  (0) 2019.10.09
CSES 3. Dynamic Programming  (0) 2019.10.09
CSES 2. Array Division (1085)  (0) 2019.10.09
CSES 2. Subarray Divisibility (1662)  (0) 2019.10.09

Dynamic Programming

  1. Dice Combinations  ( 1 )
  2. Minimizing Coins ( 1 )
  3. Coin Combinations I ( 1 )
  4. Coin Combinations II ( 1 )
  5. Removing Digits ( 1 )
  6. Grid Paths ( 1 )
  7. Book Shop ( 1 )
  8. Array Description ( 1 )
  9. Counting Towers
  10. Edit Distance ( 1 )
  11. Rectangle Cutting ( 1 )
  12. Money Sums ( 1 )
  13. Removal Game ( 0 )
  14. Two Sets II ( 0 )
  15. Increasing Subsequence ( 0 )
  16. Projects ( 0 )
  17. Counting Tilings
  18. Counting Numbers

 

CSES Problem Set 소개 ( link)

'CSES' 카테고리의 다른 글

CSES 3. Coin Combinations I (1635)  (0) 2019.10.09
CSES 3. Minimizing Coins (1634)  (0) 2019.10.09
CSES 2. Array Division (1085)  (0) 2019.10.09
CSES 2. Subarray Divisibility (1662)  (0) 2019.10.09
CSES 2. Subarray Sums II (1661)  (0) 2019.10.09

출처: https://cses.fi/problemset/task/1085


문제 설명

You are given an array containing n positive integers.

Your task is to divide the array into k subarrays so that the maximum sum in a subarray is as small as possible.

입력 

The first input line contains two integers n and k: the size of the array and the number of subarrays in the division.

The next line contains n integers x_1,x_2,,x_n: the contents of the array.

출력  

Print one integer: the maximum sum in a subarray in the optimal division.

입력 예

5 3
2 4 7 3 5

출력 예

8

Explanation: An optimal division is [2,4],[7],[3,5] where the sums of the subarrays are 6,7,8. The largest sum is the last sum 8.

제약조건

  • 1 <= n <= 2x10^
  • 1 <= k <=
  • 1 <= x_i <= 10^9

문제 풀이 

입력받은 수열의 합과 최대값을 기록한다.

수열을 가장 작은 크기로 분할하는 것은 모두 1개씩으로 분할 하는 것이다. 이때는 수열 내 원소의 최대값을 출력한다. 가장 크게 분할하면 전체를 하나로 분할하는 경우이므로, 수열의 합을 출력한다.

나머지 분할에서 나오는 값들은 이 두 값의 사이에 존재하게 되므로, high/low로 생각하고 중간값들로 분할 가능한지 확인해서, 가능한 경우에서 최소값을 출력한다.

프로그램 내용

더보기
...
bool possible(const vector<long long>& arr, int k, long long x)
{
    int div = 1;
    long long sum = 0;
    int i = 0;

    while(i < (int)arr.size())
        if(sum + arr[i] <= x)
            sum += arr[i];
            ++i;
        else
            ++div;
            sum = 0;
    return div <= k;
};

...
	vector <long long> num_arr(nNum);

    long arr_sum=0;
    long arr_max=0;

    for(int idx=0; idx < nNum; ++idx)
        long temp;
        num_arr[idx] = temp;
        arr_sum += temp;
        arr_max = max(arr_max, temp);

    if ( tSub == 1)
        cout << arr_sum <<endl;

	long long low = arr_max;
    long long high = arr_sum;

    while(low < high)
        long long mid = (low+high+1) / 2ll;

		if(possible(num_arr, tSub, mid))
			if(!possible(num_arr, tSub, mid-1))
				cout << mid;
			high = mid - 1;
		else
			low = mid + 1;
...

 

Sorting and Searching link )

'CSES' 카테고리의 다른 글

CSES 3. Minimizing Coins (1634)  (0) 2019.10.09
CSES 3. Dynamic Programming  (0) 2019.10.09
CSES 2. Subarray Divisibility (1662)  (0) 2019.10.09
CSES 2. Subarray Sums II (1661)  (0) 2019.10.09
CSES 3. Dice Combinations (1633)  (0) 2019.10.09

+ Recent posts