1. 문제

https://www.acmicpc.net/problem/1865

 

1865번: 웜홀

첫 번째 줄에는 테스트케이스의 개수 TC(1 ≤ TC ≤ 5)가 주어진다. 그리고 두 번째 줄부터 TC개의 테스트케이스가 차례로 주어지는데 각 테스트케이스의 첫 번째 줄에는 지점의 수 N(1 ≤ N ≤ 500),

www.acmicpc.net

 

2. 적용할 알고리즘

처음에는 단순히 BFS로 하다가

결국에는 알고리즘 분류를 보고 "벨만포드"를 처음으로 알게 되었다.

 

벨만포드 알고리즘은 그래프에서 간선의 값이 음수일 때 사용하여

한 노드에서 다른 노드까지의 최소 거리를 알아낼 수 있다.

 

다익스트라 알고리즘과 비슷하나 그리디한 접근 방식을 사용하지 않고

각각의 반복마다 모든 간선을 확인하여 O(VE)가 되서 다익스트라에 비해

시간이 더 오래 걸린다.

하지만 간선의 값이 음수일 때 사용할 수 있다는게 장점이다.

 

3. 풀이

이 문제에서 원하는 것은 한 노드에서 출발하여 같은

노드로 시간이 0보다 적게 걸려 도착할 수 있냐는 것이다.

이 말은 그 노드에서 출발하여 되돌아올 수 있는 음수 사이클이 존재하여

그 사이클이 돌 때마다 시간을 줄일 수 있냐는 말이 된다.

 

위 그림처럼 음수 사이클이 만들어지게 된다면

노드 1에서 다른 노드까지 걸리는 시간은 값이 -4인 간선 때문에

계속해서 작아져 음수가 될 것이고

그 노드까지 걸리는 시간이 음수가 된다면

원래 위치로 돌아 왔을 때 시간이 되돌아간 경우가 된다.

따라서, "음수 사이클이 존재한다면 시간이 되돌아간 경우가 있다"

 

음수 사이클 존재의 확인을 벨만포드 알고리즘을 통해 알 수 있다.

노드의 개수가 n일 때, n - 1번 동안

각 간선을 거쳐 해당 노드로 가는 비용을 계산하여

최단 거리 테이블을 갱신하고 난 후 한 번 더 계산을 수행한다.

만약 갱신이 또 일어난다면 그것은 음수 사이클이 존재한다는 의미이다.

아무 갱신이 일어나지 않고 최단 거리 테이블이 완성됐다면 음수 사이클이 없다는 의미이다.

 

4. 코드

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<tuple>

using namespace std;

typedef long long ll;

const int INF = 1000'0000;

vector<pair<int, int>> adj[505];

int n, m, w;

int d[505];

bool bf()
{
    fill(d, d + 505, INF);
    
    d[1] = 0;

//n - 1번 동안 모든 간선을 확인해가며 최소 비용을 계산한다.
    for (int i = 0; i < n - 1; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            for (auto nxt : adj[j])
            {
                d[nxt.first] = min(d[nxt.first], d[j] + nxt.second);
            }
        }
    }

//음수 사이클이 존재하는지 확인한다.
    for (int j = 1; j <= n; j++)
    {
        for (auto nxt : adj[j])
        {
            if (d[j] + nxt.second < d[nxt.first]) return true;
        }
    }

    return false;
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        cin >> n >> m >> w;

        for (int i = 1; i <= n; i++) adj[i].clear();

        for (int i = 0; i < m; i++)
        {
            int s, e, t;
            cin >> s >> e >> t;

            adj[s].push_back({ e, t });
            adj[e].push_back({ s, t });
        }

        for (int i = 0; i < w; i++)
        {
            int s, e, t;
            cin >> s >> e >> t;

            adj[s].push_back({ e, -t });
        }
        cout << (bf() ? "YES" : "NO") << '\n';
    }


}

5. 느낀 점

그래프 문제에서 다익스트라, 벨만포드, 플로이드 알고리즘은

가끔 나오긴 하지만 유용하게 쓰일 때가 있으니 이에 대한

배경지식을 잘 쌓아놓도록 하자...

1. 문제

https://www.acmicpc.net/problem/9019

 

9019번: DSLR

네 개의 명령어 D, S, L, R 을 이용하는 간단한 계산기가 있다. 이 계산기에는 레지스터가 하나 있는데, 이 레지스터에는 0 이상 10,000 미만의 십진수를 저장할 수 있다. 각 명령어는 이 레지스터에

www.acmicpc.net

 

2. 적용할 알고리즘

큐에 명령을 사용한 순서를 string으로 저장하고 bfs를 돌리면 될 것 같다

 

3. 풀이

큐에서 숫자를 뽑아내 다음 숫자를 선택하는 연산을 그 때 마다 수행하니 시간 초과가 났다.

따라서 res[i][j] 이차원 배열에 숫자 i에서 연산을 하여 나오는 숫자를 j(0, 1, 2, 3)에 미리 저장해놓고

테스트케이스마다 저장해놓은 정보를 가져다 쓰기만 하면 시간을 줄일 수 있다.

 

시간 제한이 좀 애매하긴 했다. 고작 1/4 줄인다고 해결될 것 같지 않아서 이 방식을 생각하지 않은 것 같다.

테스트 케이스가 여러 개 주어지는 문제에서 미리 필요한 정보를 계산해놓고 정보를 가져다 쓰는 방식을 사용하여 시간을 줄일 수 있다는 걸 기억하자.

 

4, 코드

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<tuple>

using namespace std;

typedef long long ll;

string vis[10005];

int FuncD(int num)
{
    return num * 2 % 10000;
}

int FuncS(int num)
{
    if (num == 0) num = 9999;
    else num--;

    return num;
}

int FuncL(int num)
{
    return (num % 1000) * 10 + num / 1000;
}

int FuncR(int num)
{
    return (num % 10) * 1000 + num / 10;
}

int res[10005][4];

char c[4] = { 'D', 'S', 'L', 'R' };

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    int t;
    cin >> t;

    for (int i = 0; i <= 9999; i++)
    {
        res[i][0] = FuncD(i);
        res[i][1] = FuncS(i);
        res[i][2] = FuncL(i);
        res[i][3] = FuncR(i);
    }

    while (t--)
    {
        vector<bool> vis(10005);

        int a, b;
        cin >> a >> b;

        string s;

        queue<pair<int, string>> Q;
        Q.push({ a, "" });
        vis[a] = 1;

        while (!Q.empty())
        {
            int cn;
            string cs;
            tie(cn, cs) = Q.front(); Q.pop();

            if (cn == b)
            {
                cout << cs << '\n';
                break;
            }

            for (int i = 0; i < 4; i++)
            {
                int nn = res[cn][i];
                string ns = cs;

                if (vis[nn]) continue;

                ns.push_back(c[i]);
                Q.push({ nn, ns });
                vis[nn] = 1;
                
            }
        }
    }
}

1. 문제

https://www.acmicpc.net/problem/5525

 

5525번: IOIOI

N+1개의 I와 N개의 O로 이루어져 있으면, I와 O이 교대로 나오는 문자열을 PN이라고 한다. P1 IOI P2 IOIOI P3 IOIOIOI PN IOIOI...OI (O가 N개) I와 O로만 이루어진 문자열 S와 정수 N이 주어졌을 때, S안에 PN이 몇

www.acmicpc.net

2. 적용할 알고리즘

문자열...?

 

3. 풀이

처음에는 c++ string의 find를 이용해 풀려고 했지만 n과 m이 100만으로 아주 크기 때문에

새로운 방법을 생각해야 한다.

 

문제에서 원하는 건 IOI, IOIOI, IOIOIOI.... 가 입력받은 문자열에 몇 번 들어가는지를 알아내는 것이다.

여기서 눈에 띄는 건 P(n)이 항상 IOI를 포함하고 있다는 것이고 n은 IOI가 몇 번 포함되었는지를 나타낸다.

따라서 IOI가 연속으로 몇 번 나왔는지를 체크하면 된다.

예를 들어, n이 3일 때 IOI가 연속으로 3번나왔다면 IOIOIOI가 문자열에 포함되었다는 뜻이 된다.

 

이를 구현한 코드는 다음과 같다.

 

4. 코드

#include<iostream>
#include<string>
#include<vector>
#include<queue>

using namespace std;

typedef long long ll;

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    int n, m;
    string s;

    cin >> n;
    cin >> m;
    cin >> s;

    int ans = 0;

    for (int i = 0; i < s.size(); i++)
    {
        int k = 0;

        if (s[i] == 'I')
        {
            while (s[i + 1] == 'O' && s[i + 2] == 'I')
            {
                k++;
                if (n == k)
                {
                    ans++;
                    k--;
                }
                i += 2;
            }
        }
    }

    cout << ans;
}

1.Win32 API 프로그래밍 구조

#WinMain()

1)윈도우 구조체 설정, 등록

2)윈도우 생성과 출력

3)메시지 루프

: 프로그램이 종료될때까지

자신에게 보내진 메세지가 있는지 계속 확인하면서

받은 메세지가 있으면 전달함

 

#WndProc() 윈도우 프로시져

- 메시지 처리

- 메시지? => WM_xxx, CB_xxx, LM_xxx, PBM_xxx 등으로 모두 양의 값

 

윈도우 프로시져가 메세지를 처리하는 코드는 다음과 같다.

//프로시져의 메시지 처리 방식
switch(message)
{
	//각각의 메시지에 해당하는 처리
	case 메시지1:
    	break;
    case 메시지2:
    	break;
    case 메시지3:
    	break;
    
    //처리하지 않은 메시지에 대한 기본적인 처리
    default:
    	DefWndProc();
}

 

#메시지 전달과 처리 과정

 

-메시지 루프와 WndProc()

while( GetMessage(&msg, NULL, 0, 0))
//메시지를 큐를 검사하면서 해당 응용프로그램의 메세지가 있는지 체크한다
{
	TranslateMessage(&msg);//키보드 입력이 들어왔을때 어떤 문자가 눌렸는지 정보를 생성
    DispatchMessage(&msg);//메세지를 윈도우 프로시져에게 보내주고
    //윈도우 프로시져에게 처리되어 리턴된 값을 받아온다.
}

 

2. 프로젝트 생성, 문자집합

#멀티 바이트 문자 집합(MBCS)

- 1 ~ 2 Byte 크기의 문자 집합

(1 Byte로 표현할 수 없는 중국어, 일본어 등 지원)

- 영문과 한글 지원

- 일반 C함수는 MBCS 문자집합 사용

 

#유니코드 문자 집합

-2Byte로 전세계 언어를 표현하는 문자집합

-유니코드는 멀티바이트에 비해 메모리는

많이 차지하지만 처리속도는 더 빠름

-ASCII 코드 값의 일부는 유니코드와 같다. (문자, 숫자)

 

#문자 집합에 따른 데이터형 변환

-문자

TCHAR (ANSI,아스키) => CHAR(char)

TCHAR(Unicode) => WCHAR(wchar_t)

 

-문자열

LPTSTR(ANSI, 아스키) => LPSTR(char*)

LPTSTR(Unicode) => LPWSTR(wchar_t*)

 

-ANSI 문자열을 유니코드로 변환 매크로 함수, TEXT()

LPTSTR str = TEXT("문자열")

 

3. 윈도우 프로그래밍 기본 함수

#WinMain() 함수의 원형

#include<Windows.h>

#define WINAPI __stdcall

int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
				   LPSTR IpCmdLine,
				   int nCmdShow){}

int APIENTRY _tWinMain() //Unicode 사용에 따라 달라지는 함수

- hInstance : 응용 프로그램 종류의 인스턴스 값

- hPrevInstance : 이전 인스턴스 값

- IpCmdLine : 프로그램 외부에서 내부로 값 설정

- nCmdShow : 윈도우 출력 형태에 관한 값

 

#WndProc() 함수의 원형

LRESULT CALLBACK WndProc(HWND hWnd,
	UINT message, WPARAM wParam,
	LPARAM lParam)
{
	return 0;
}

4. 윈도우 프로그래밍 WinMain() 작성 순서

 

#WinMain() 작성 순서 1단계, 구조체 설정

		WNDCLASSEX wcex;
	{
		wcex.cbSize = sizeof(WNDCLASSEX); //구조체의 크기
		wcex.style = CS_HREDRAW | CS_VREDRAW; //윈도우 이동 또는 크기가 가로세로 변할 때 윈도우 그리기
		wcex.lpfnWndProc = WndProc; //함수 포인터(호출하고자 하는 함수를 연결)
		wcex.cbClsExtra = 0; //여유분
		wcex.cbWndExtra = 0;
		wcex.hInstance = hInstance; //매개변수로 받은 인스턴스 값을 받음
		wcex.hIcon = LoadIcon(hInstance,
			MAKEINTRESOURCE(IDI_APPLICATION)); //아이콘
		//MAKEINTRESOURCE : 값을 양의 정수로 바꿔주는 매크로
		//IDI_APPLICATION : 기본 아이콘
		wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
		//커서
		//IDC_ARROW : 기본 커서
		wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		//배경
		//COLOR_WINDOW + 1 : 흰색
		wcex.lpszMenuName = NULL;
		//메뉴
		//NULL : 기본메뉴
		wcex.lpszClassName = (LPWSTR)"basic";
		//구조체를 OS에 등록할 때 이름
		wcex.hIconSm = LoadIcon(wcex.hInstance,
			MAKEINTRESOURCE(IDI_APPLICATION));
		//Sm(all)아이콘
	}

 

#WinMain() 작성 순서 2단계, 구조체 등록

	{
		RegisterClassEx(&wcex);
		//구조체 등록 매크로 함수, 매개 변수 : 구조체 주소
	}

 

#WinMain() 작성 순서 3단계, 윈도우 생성

	HWND hWnd;
	{
		hWnd = CreateWindow((LPCWSTR)"basic", (LPCWSTR)"HelloWorld",
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			800, 600, NULL, NULL,
			hInstance, NULL);

		//#define CW_USEDEFAULT : 운영체제의 기본 위치값으로 설정
	}

 

#WinMain() 작성 순서 4단계, 윈도우 출력

	{
		ShowWindow(hWnd, nCmdShow);
		//윈도우 출력

		UpdateWindow(hWnd);
		//윈도우 업데이트

		//또는

		ShowWindow(hWnd, SW_SHOW);
		UpdateWindow(hWnd);
	}

 

#WinMain() 작성 순서 5단계, 메시지 루프

	{
		//# 메시지 루프를 구성하는 함수

		BOOL GetMessage(
			LPMSG lpMsg,
			HWND hWnd,
			UINT wMsgFilterMin,
			UINT wMsgFilterMax);
		//GetMessage()
		//메시지 큐에서 메시지를 가져오는 역할
		//WM_QUIT(완전히 프로그램을 빠져나갈때)가 발생할 때만 FALSE 리턴
		//그 외에는 TRUE 리턴


		BOOL TranslateMessage(const MSG * lpMsg);
		//문자 키 또는 키 입력에 대한 메시지 변환
		//WM_KEYDOWN => WM_CHAR


		LRESULT DispatchMessage(const MSG * lpMsg);
		//WndProc() 함수 호출
		//WndProc()가 종료될 때까지 대기
		//WndProc()로 메시지 전달

		//MSG 구조체
		typedef struct {
			HWND hwnd;
			UINT message; //메세지
			WPARAM wParam;//메시지와 함께 전달되는 정보
			LPARAM lParam;//메시지와 함께 전달되는 정보
			DWORD time;
			POINT pt;
		} MSG, *PMSG;

		//메시지 루프 전체 구조
		LPMSG msg;
		while (GetMessage(&msg, NULL, 0, 0))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

5. WndProc() 작성

# WndProc() 기본 메세지

-WM_PAINT

:최초 UpdateWindow() 함수에 의해 발생,

윈도우의 일부 영역을 새로 출력할 때 발생

 

-WM_DESTROY

:윈도우가 화면에서 사라진 후에 보내지는 메시지,

 메모리에서 제거되기 직전에 보내짐

 

# WndProc() 구성

LRESULT CALLBACK WndProc(HWND hWnd,
	UINT message, WPARAM wParam,
	LPARAM lParam)
{

	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps); //화면 그리기 준비

		EndPaint(hWnd, &ps); //화면 그리기 종료
		break;

	case WM_DESTROY:

		//메모리 해제에 대한 코드가 들어가는 곳//

		PostQuitMessage(0); //WM_QUIT 발생
		break;

	default:
		return DefWindowProc(hWnd, message,
			wParam, lParam);

		return 0;
	}

	return 0;

 

6. 자동 생성 (VS 2022 버전)

// Windows API_AUTO.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//

#include "framework.h"
#include "Windows API_AUTO.h"

#define MAX_LOADSTRING 100

// 전역 변수:
HINSTANCE hInst;                                // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열을 초기화합니다.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSAPIAUTO, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화를 수행합니다:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSAPIAUTO));

    MSG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  함수: MyRegisterClass()
//
//  용도: 창 클래스를 등록합니다.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSAPIAUTO));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSAPIAUTO);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   함수: InitInstance(HINSTANCE, int)
//
//   용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
//   주석:
//
//        이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
//        주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  용도: 주 창의 메시지를 처리합니다.
//
//  WM_COMMAND  - 애플리케이션 메뉴를 처리합니다.
//  WM_PAINT    - 주 창을 그립니다.
//  WM_DESTROY  - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석합니다:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

#MyRegisterClass()

: 윈도우 구조체 설정 및 등록

 

#InitInstance()

: 윈도우 생성 및 출력

 

#About()

: 정보 대화 상자의 메시지 처리기, 애플리케이션 정보 같은 것들 표시

 

# HACCEL hAccelTable;

: 단축기에 대한 것

 

# LoadString()

: 문자열을 리소스를 다루기 위해, 문자열에 대한 아이디를 부여하고, 프로그램을 쓸 때

아이디를 이용해 문자열을 해당 배열에 복사한다.

아이디가 부여된 문자열, 그리고 그에 대한 값

솔루션 탐색기/리소스 파일/rc파일/String Table

경로에 저장 되어 있다.

 

위의 모든 내용은

유튜브 아워즈팜X나우캠퍼스, "Win32 API 2강. Win32 API 프로그래밍 구조",

'Win32 API' 카테고리의 다른 글

윈도우 프로그래밍 기초  (1) 2023.04.17

1. 배열을 시계방향으로 돌리면 인덱스가 어떻게 바뀌는가?

인덱스는 이렇게 변한다, 자세히 보다보면 규칙이 보인다.

그 규칙은 바로

원래 인덱스가 (i, j)라면 바뀐 인덱스는 (j, n - 1 - i ) (n은 원래 배열의 세로길이)

//-1을 하는 이유는 배열이 0부터 시작이기 때문...

 

2. 반시계방향은?

반시계 방향은 원래 인덱스 (i, j)에서 (n - 1 - j, i)가 된다.

 

#그리고 만약 배열의 가로, 세로길이가 다르다면 가로 세로길이를 교환해야 한다.

 

3. 코드

void Rotate(int dir) //-1 : 반시계, 1 : 시계 방향
{

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            if (dir == -1) tmp[n - j - 1][i] = board[i][j];// 배열이 0부터 시작이라면 -1
            else tmp[j][n - i - 1] = board[i][j];
        }
    }

    swap(n, m);

    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++) board[i][j] = tmp[i][j];
}

 

1. 문제

https://www.acmicpc.net/problem/1799

 

1799번: 비숍

첫째 줄에 체스판의 크기가 주어진다. 체스판의 크기는 10이하의 자연수이다. 둘째 줄부터 아래의 예와 같이 체스판의 각 칸에 비숍을 놓을 수 있는지 없는지에 대한 정보가 체스판 한 줄 단위로

www.acmicpc.net

2. 문제의 요구

체스에서 대각선으로 공격할 수 있는 "비숍"을 서로 공격하지 못하도록

체스판에 배치할 수 있는 최대 개수를 구하여라.

 

3. 풀이

#적용할 알고리즘 : 백트래킹

일단은 단순하게 모든 칸에 대해 각 비숍을 배치할 때 마다

그 대각선 방향에 전에 배치했던 비숍의 유무를 확인하고 없다면

그 자리에 배치하는 방식의 알고리즘을 생각할 수 있다.

하지만 이런 방식은 시간초과가 났다.

여기서 새로운 발상이 필요한데 그것은 바로

#새로운 발상 : 비숍을 놓을 때 체스판의 검은 부분과 흰 부분은 서로 관련이 없다.

비숍을 흰 부분에 놓으면 그 비숍이 공격할 수 있는 곳은 흰 부분 뿐이다.

따라서 체스판의 흰 부분과 검은 부분을 분리하여 따로 백트래킹한다.

이렇게 하면 (10 * 10) = 100 에서 ((5 * 5) * 2) = 50 이 되기 때문에 확실한 이득을 볼 수 있다.

 

4. 코드

#include<iostream>
#include<string>
#include<stack>
#include<tuple>
#include<queue>
#include<algorithm>
#include<bitset>

using namespace std;

#define endl (cout << '\n')

int dx[4] = { 1, -1, 1, -1 };
int dy[4] = { 1, -1, -1, 1 };

int n;

int board[12][12];
bool isUsed[12][12];

vector<pair<int, int>> white;
vector<pair<int, int>> black;

int mx = 0;

//대각선 라인에 다른 비숍이 있는지 확인
//있으면 => 되돌아감
//없으면 => 계속
bool Check(int x, int y)
{
    for (int dir = 0; dir < 4; dir++)
    {
        for (int i = 0; true; i++)
        {
            if (x + i * dx[dir] < 0 || x + i * dx[dir] >= n || y + i * dy[dir] < 0 || y + i * dy[dir] >= n) break;
            //cout << x << ' ' << y << '\n';
            //cout << i << ' ' << dx[dir] << ' ' << dy[dir] << ' ' << x + i * dx[dir] << ' ' << y + i * dy[dir] << '\n';
            if (isUsed[x + i * dx[dir]][y + i * dy[dir]])
            {
                return false;
            }
        }
    }
    return true;
}

//백트래킹
void solve(int cur, vector<pair<int, int>>& color, int start)
{
    mx = max(mx, cur);

    if (cur == color.size()) return;

    for (int i = start; i < color.size(); i++)
    {
        int cx, cy;
        tie(cx, cy) = color[i];

        if (isUsed[cx][cy]) continue;

        if (Check(cx, cy))
        {
            isUsed[cx][cy] = 1;
            solve(cur + 1, color, i + 1);
            isUsed[cx][cy] = 0;
        }
    }
}

bool isWhite[12][12];

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n;

    for (int i = 0; i < n; i += 2) isWhite[0][i] = 1;
    for (int i = 1; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            isWhite[i][j] = (isWhite[i - 1][j] ? false : true);
        }
    }

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> board[i][j];
            if (board[i][j] == 1)
            {
            ///화이트 칸과 블랙 칸을 나눠서 벡터에 저장
                if (isWhite[i][j]) white.push_back({i, j});
                else black.push_back({ i, j });
            }
        }
    }

    int ans = 0;

	//화이트 칸과 블랙칸이
	//서로 연관이 없기 때문에 가능하다
	//화이트 칸에서 비숍 배치
    solve(0, white, 0);
    ans += mx;
    mx = 0;
    //블랙 칸에서 비숍 배치
    solve(0, black, 0);
    ans += mx;

    cout << ans;

}

5. 느낀 점

시간을 줄일 방법이 생각이 안나서 결국 답을 찾아봤다.

이런 발상을 좀 더 쉽게 할 수 있도록 계속 생각해야한다.

1. Win32 API 프로그래밍 개요

- Win32 API 프로그래밍이란?

윈도우 운영체제가 제공하는 함수를 이용한 C 언어 기반의 프로그래밍이다.

 

-프로그래밍 방식의 변화

윈도우 운영체제 이전

: 프로그래밍이 운영체제를 제어, 모든 프로그래밍을 프로그래머가 전담했다

키보드, 마우스 등을 직접 프로그래밍했다.

 

윈도우 운영체제 이후

:  키보드, 마우스 등이 운영체제에 드라이버의 형태로 모두 들어갔다,

운영체제가 응용 프로그램에 메시지를 보내줌으로써 모든 정보를 얻는다. 

 

2. 프로그래밍 구조 비교

-프로그래밍 형식의 변화

C: main()

=> WinMain() : 응용 프로그램 윈도우 생성

=> WinProc() : 메시지 처리

 

3. 윈도우 데이터형

윈도우의 데이터형은 아주 많다(MSDN에 검색하여 알 수 있다)

하지만 모든 데이터형을 외울 필요가 없고 자주 쓰이는 것들만 이번에 알아가보자.

윈도우의 데이터형은 기존 C의 데이터형을 윈도우 스타일로 재정의한 것이다.

데이터형 정의
BYTE typedef unsigned char BYTE;
BOOL typedef int BOOL;
CHAR typedef char CHAR;
COLORREF(색상을 저장하기 위한 데이터형) typedef DWORD COLORREF
DWORD typedef unsigned long DWORD
데이터형 정의
PVOID typedef void* PVOID;
HANDLE typedef PVOID HANDLE;
HDC typedef HANDLE HDC;
HGDIOBJ typedef HANDLE HGDIOBJ;
HINSTANCE typedef HANDLE HINSTANCE;
HWND typedef HANDLE HWND
LPVOID typedef void* LPVOID:
LPWORD typedef WORD* LPWORD;
데이터형 정의
LPSTR typedef CHAR* LPSTR;
LPWSTR typedef WCHAR *LPWSTR;
LPTSTR #ifdef UNICODE //유니코드를 사용하는 프로젝트에서...
     typedef LPWSTR LPTSTR;
#else //일반 아스키코드를 사용하는 프로젝트에서...
     typedef LPSTR LPTSTR;
#endif

TCHAR #ifdef UNICODE
    typedef WCHAR TCHAR;
#else
    typedef char TCHAR;
데이터형 정의
UINT typedef unsigned int UINT;
VOID #define VOID void
WINAPI #define WINAPI __stdcall
WORD typedef unsigned short WORD;
0 ~ 65535
WPARAM typedef UINT_PTR WPARAM;
UINT_PTR #if defined(_WIN64) //64비트 운영체제
    typedef unsigned __int64 UINT_PTR
#else
    typedef unsigned int UINT_PTR
LPARAM typedef LONG_PTR LPARAM;
LONG_PTR #if defined(_WIN64)
    typedef __int64 LONG_PTR;
#else
    typedef long LONG_PTR;
#endif

 

4. 인스턴스와 핸들

운영체제는 멀티 태스킹 운영체제이다.

같은 메모장에 다른 데이터가 들어가 있다.

이 응용 프로그램들을 운영체제는 어떻게 다루고 구분하는가?

=> 인스턴스(Instance)와 핸들(Handle)

 

-인스턴스와 핸들의 실체?

HINSTANCE, HWND => HANDLE이고

HANDLE => PVOID,

PVOID => void* : 4 바이트의 양의 정수값

 

#인스턴스란 무엇인가?

-응용프로그램의 아이디(절대 중복되지 않는다.)

-같은 종류의 프로그램은 같은 인스턴스를 가진다.

ex) 메모장의 인스턴스가 14745600이라면

메모장 창이 여러 개 켜져 있을 때 그것들의 인스턴스는 모두 14745600이다.

 

#핸들이란 무엇인가?

-운영체제에서 할당한 자원의 아이디

-같은 종류의 응용 프로그램이라 할지라도

창마다 다른 핸들을 가진다.

#공통점

1) 운영체제에서 할당하는 값

2) 중복되지 않는 값 => 아이디의 속성

 

#윈도우 프로그래밍에서 가장 중요한 핸들

앞에 H가 붙는 데이터형은 전부 핸들이다.

HWND, HDC, HPEN, HBRUSH 등 => void*

 

5. 헝가리안 표기법

#헝가리안 표기법이란 무엇인가?

변수, 함수명의 명명 규칙이다.

변수명만으로 용도 파악이 용이

 

#규칙

1)의미 있는 단어를 연결하고 첫 문자는 대문자로 사용한다

2)데이터형을 의미하는 접두사를 붙인다 ex) fCount, nNumber(i'n't)

데이터형 접두사
BOOL, bool b
char ch, c
int, short i, n
long l
float f
double d
배열 a
DWORD dw
문자열 sz, s, str
포인터 p
핸들 h
전역변수 g_
ex) g_nVariable
윈도우 메시지 msg

 

위의 모든 내용은

유튜브 아워즈팜X나우캠퍼스, "Win32 API 1강. Win32 API 프로그래밍 구조",

 

 

'Win32 API' 카테고리의 다른 글

Win32 API 프로그래밍 구조  (0) 2023.04.18

1. 문제

https://www.acmicpc.net/problem/2580

 

2580번: 스도쿠

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루

www.acmicpc.net

2. 문제의 요구

빈칸을 채워 스도쿠를 완성하라!

 

3. 적용할 알고리즘

백트래킹이 가능하다.

처음에는 빈칸이 10개인 경우 9의 10승으로 시간초과가 날 거라 예상했지만

빈칸을 채울 때마다 조건 확인을 해주면 그 다음 재귀로 넘어가는 경우의 수는 많이 줄어든다.

 

4. 설명

빈칸에 1~9까지 수를 대입해보고

스도쿠 조건에 만족하지 않는다면 그 경우는 패스하고,

만족한다면 다음 빈칸으로 넘어간다.

만약 만족하는 수가 없다면 이전 선택이 잘못된 것이므로

이전의 빈칸으로 돌아간다.

빈칸이 모두 채워졌다면 종료한다.

 

5. 코드

#include<iostream>
#include<vector>
#include<string>
#include<stack>
#include<queue>
#include<tuple>

using namespace std;

const int INF = 0x7f7f7f7f;

typedef long long ll;

int dx[4] = { 1, -1, 0, 0 };
int dy[4] = { 0, 0, 1, -1 };

int board[10][10];

vector<pair<int,int>> zero;

bool Check(int x, int y, int num)
{
	for (int i = 0; i < 9; i++)
	{
		if (board[i][y] == num) return false;
		if (board[x][i] == num) return false;
	}

//주변 9개 칸 중 같은 것이 있는지 확인
//*주의* C언어에서 x / 3 * 3 != x 이다. 
// '/'연산은 나머지를 날려버리기 때문이다.
	for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i++)
	{
		for (int j = y / 3 * 3; j < y / 3 * 3 + 3; j++)
		{
			if (board[i][j] == num) return false;
		}
	}

	return true;
}

int ans[10][10];

void solve(int cur)
{
//빈 칸을 다 채웠을 시 종료
	if (cur == zero.size())
	{
		for (int i = 0; i < 9; i++)
			for (int j = 0; j < 9; j++) ans[i][j] = board[i][j];
		return;
	}

	int cx, cy;
	tie(cx, cy) = zero[cur];

//적절한 수인지 확인 후 다음 빈칸으로 넘어간다
	for (int i = 1; i <= 9; i++)
	{
		if (Check(cx, cy, i))
		{
			board[cx][cy] = i;
			solve(cur + 1);
			board[cx][cy] = 0;
		}
	}
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);

	for (int i = 0; i < 9; i++)
	{
		for (int j = 0; j < 9; j++)
		{
			cin >> board[i][j];
			if (board[i][j] == 0) zero.push_back({ i, j });
		}
	}

	solve(0);

	for (int i = 0; i < 9; i++)
	{
		for (int j = 0; j < 9; j++) cout << ans[i][j] << ' ';
		cout << '\n';
	}
}

6. 느낀 점

그냥 대충 순열, 조합 등으로 계산하고 백트래킹 불가로 생각하지 말고

어떠한 조치를 취하면 백트래킹의 시간복잡도가 대폭 줄어들어

완전탐색이 가능하다는 걸 기억하자

'알고리즘&자료구조 실전문제풀이 > 백트래킹' 카테고리의 다른 글

Boj 1799 비숍  (0) 2023.04.18
boj 16987 계란으로 계란치기  (0) 2023.04.14

1. 문제

https://www.acmicpc.net/problem/1002

 

1002번: 터렛

각 테스트 케이스마다 류재명이 있을 수 있는 위치의 수를 출력한다. 만약 류재명이 있을 수 있는 위치의 개수가 무한대일 경우에는 -1을 출력한다.

www.acmicpc.net

2. 문제에서 원하는 것

A와 B의 좌표가 주어지고 A와 C, B와 C 사이의 거리가

주어질 때 C가 있을 수 있는 위치의 개수는?

3. 적용할 알고리즘

기하, 두 원의 교점

4. 설명

C와의 거리를 이용하면 C가 있을 수 있는 곳을 원으로 표현 가능 하다.

따라서 두 점으로 부터의 거리를 모두 만족하는

두 원의 교점이 C가 있을 수 있는 점이된다.

그리고 두 점 사이의 거리 dis, r1 + r2와 r1 - r2(r1 > r2일 때)의 값을 이용해

교점이 몇 개 생기는지 알 수 있다.

1) dis > r1 + r2

교점이 생기지 않는다.

 

2) dis == r1 + r2

교점이 하나 생긴다(외접)

 

3) dis < r1 + r2

의 경우는 r1 - r2에 따라 또 세 가지로 나뉜다.

 

3-1) dis > r1 - r2

교점이 두개 생긴다

 

3-2) dis == r1 - r2

교점이 하나 생긴다(내접)

 

3-3) dis < r1 - r2

큰 원 안에 작은 원이 있다, 교점이 없다

 

예외) x1 == x2 && y1 == y2 && r1 == r2

두 원이 완전히 겹친다 => 교점이 무한하다.

 

5. 코드

#include<iostream>
#include<vector>
#include<string>
#include<stack>
#include<queue>
#include<tuple>

using namespace std;

const int INF = 0x7f7f7f7f;

typedef long long ll;

int dx[4] = { 1, -1, 0, 0 };
int dy[4] = { 0, 0, 1, -1 };

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);

	int t;
	cin >> t;
	while (t--)
	{
		int x1, y1, r1, x2, y2, r2;
		cin >> x1 >> y1 >> r1 >> x2 >> y2 >> r2;

		int dis = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
		int bigger, smaller;
		if (r1 < r2)
		{
			bigger = r2;
			smaller = r1;
		}
		else
		{
			bigger = r1;
			smaller = r2;
		}

		int sum = bigger + smaller; sum *= sum;
		int sub = bigger - smaller; sub *= sub;

		int ans = 0;

		if (dis > sum)//접하지 않음
			ans = 0;
		else if (dis == sum) //외접한다
			ans = 1;
		else
		{
			if (dis > sub) // 두 점에서 만난다
				ans = 2;
			else if (dis == sub) // 내접한다
				ans = 1;
			else //큰 원 안에 작은 원이 있다
				ans = 0;
		}

		if (r1 == r2 && x1 == x2 && y1 == y2) //똑같다 == 교점이 무한
			ans = -1;

		cout << ans << '\n';
	}
}

6. 느낀 점

이러한 기초적인 수학, 기하학적 논리는 기억해두도록 하자...

 

1. 문제 :https://www.acmicpc.net/problem/16987

 

16987번: 계란으로 계란치기

원래 프로그래머의 기본 소양은 팔굽혀펴기를 단 한 개도 할 수 없는 것이라고 하지만 인범이는 3대 500을 넘기는 몇 안되는 프로그래머 중 한 명이다. 인범이는 BOJ에서 틀린 제출을 할 때마다 턱

www.acmicpc.net

2. 문제에서 원하는 것

계란을 왼쪽에서 오른쪽으로 한 칸씩 움직이며 맨 오른쪽 끝에 있는 계란 까지 손에 들고

다른 임의의 계란을 들어서 부딪혔을 때 계란을 최대 몇 개 깰 수 있는가?

 

3. 적용할 알고리즘

총 계란의 개수가 8개이므로 계란을 들고 어떤 계란과 부딪힐지 모든 경우의 수를

조사하면 총 연산 횟수는 대략 최대 7의 8승(5,764,801)이 되어서

백트래킹을 이용한 완전 탐색이 가능하다.

 

4. 수학적 사고, 새로운 발상

필요 없음, 그냥 단순한 백트래킹

 

5. 코드

#include<iostream>
#include<string>
#include<stack>
#include<tuple>
#include<queue>
#include<algorithm>
#include<bitset>

using namespace std;

int n;

pair<int, int> egg[10];

int ans = 0;

void solve(int cur)
{
    if (cur == n)
    {
        int cnt = 0;
        for (int i = 0; i < n; i++)
        {
            //cout << egg[i].first << ' ';
            if (egg[i].first <= 0) cnt++;
        }
        ans = max(ans, cnt);
        return;
    }

    if (egg[cur].first <= 0)
    {
        solve(cur + 1);
        return;
    }

    for (int i = 0; i < n; i++)
    {
        if (cur == i) continue;
        if (egg[i].first <= 0) continue;

        egg[cur].first -= egg[i].second;
        egg[i].first -= egg[cur].second;
        solve(cur + 1);
        egg[cur].first += egg[i].second;
        egg[i].first += egg[cur].second;
    }

    if (cur == n - 1) solve(cur + 1);
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n;
    
    for (int i = 0; i < n; i++) cin >> egg[i].first >> egg[i].second;

    solve(0);

    cout << ans;
}

6. 느낀 점

쉬운 문제인데 문제 잘못읽어서 1시간 동안 뻘짓했다ㅋㅋ

앞으로는 문제를 잘 읽고 풀이가 확실히 떠오를 때 구현을 시작하자...

'알고리즘&자료구조 실전문제풀이 > 백트래킹' 카테고리의 다른 글

Boj 1799 비숍  (0) 2023.04.18
Boj 2580 스도쿠  (0) 2023.04.16

+ Recent posts