문제

네 자연수 A, B, C, D가 주어진다. 이때, A와 B를 붙인 수와 C와 D를 붙인 수의 합을 구하는 프로그램을 작성하시오.

두 수 A와 B를 합치는 것은 A의 뒤에 B를 붙이는 것을 의미한다. 즉, 20과 30을 붙이면 2030이 된다.

 

 

입력

첫째 줄에 네 자연수 A, B, C, D가 주어진다. (1 ≤ A, B, C, D ≤ 1,000,000)

 

출력

A와 B를 붙인 수와 C와 D를 붙인 수의 합을 출력한다.

 

 


 

 

문제 해결

머리가 복잡해서 식힐겸 오랜만에 아주아주 간단한 문제를 풀어봤다. string을 이용하면 한 줄이면 끝나지만 일부러 돌아왔다. ( string 사용 시 : cout << stol(a + b) + stol(c + d); )

 

b의 자릿수만큼 a를, d의 자릿수만큼 c를 늘리면 된다.

그런 다음 a에 b를 더한 수와 c에 d를 더한 수를 더해주면 구하는 결과가 나온다.

 

 

 

코드

#include <iostream>
using namespace std;

long long a, b, c, d;

int main() {

	cin >> a >> b >> c >> d;

	int tmp = b;
	while (tmp) {
		a *= 10;
		tmp /= 10;
	}
	a += b;

	tmp = d;
	while (tmp) {
		c *= 10;
		tmp /= 10;
	}
	c += d;

	cout << a + c;
}

'🎲 BOJ > 🥉' 카테고리의 다른 글

[C++] 백준 25304 : 영수증  (0) 2023.02.01
[C++] 백준 13305 : 주유소  (0) 2022.01.27

 

 

 

 

ViewPager2(뷰페이저2)

 

뷰페이저는 스와이프(손가락으로 화면을 탭하여 오른쪽이나 왼쪽으로 미는) 이벤트로 화면을 전환할 때 사용합니다. 우리가 사용하는 앱에서 흔히 볼 수 있는 화면이므로 이를 구성하려면 알아둘 필요가 있습니다. 2019년에 제공된 뷰페이저2는 기존의 뷰페이저 라이브러리보다 많은 기능을 제공하고 있으므로 뷰페이저2로 사용할 것을 권장드립니다.

 

 

뷰페이저2를 이용하려면 먼저 모듈 수준 그래들 파일의 dependencies 항목에 다음처럼 선언해야 합니다.

// 뷰 페이저2 선언

implementation 'androidx.viewpager2:viewpager2:1.0.0'

 

 

 

그리고 레이아웃 XML 파일에 뷰페이저2를 추가합니다.

// activity_main.xml 파일에 뷰페이저2 라이브러리 추가

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

 

 

뷰페이저2는 화면을 항목으로 봅니다. 각 항목이 순서대로 나열되어 있는데 단지 한 화면에 하나의 항목이 나온다는 개념입니다. 따라서 리사이클러뷰에서 살펴본 어댑터를 적용해야 합니다. 뷰페이저2에 사용할 수 있는 어댑터는 2가지인데, 리사이클러뷰에서 봤던 RecyclerView.Adapter를 그대로 이용하거나 FragmentStateAdapter를 사용할 수도 있습니다.

 

 


 

리사이클러뷰 어댑터를 이용한 뷰페이저2 구현

 

뷰페이저2를 리사이클러뷰 어댑터로 구현하는 경우는 화면 구성이 같을 때에 이용합니다. 이를 위해서는 XML 화면 구성을 뷰홀더에 적용하기 위해, 별도로 구성된 XML 파일이 있어야 합니다. 저는 item_main.xml이라는 파일을 생성하여 간단한 화면 구성으로 작성했습니다.

// item_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/item_text"
        android:textSize="30sp"
        android:layout_gravity="center_vertical"
        android:textColor="@color/white"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

 

 

 

그런 다음 뷰홀더와 뷰어댑터 클래스를 작성합니다. 각각의 클래스는 RecyclerView에서 해당 인터페이스를 상속받습니다. 아래의 뷰어댑터는 화면 3개를 뷰페이저2로 제공하는 어댑터입니다.

// 뷰페이저2 구현 - 리사이클러뷰 어댑터 이용

// 뷰홀더
class MyPagerViewHolder(val binding: ItemMainBinding): RecyclerView.ViewHolder(binding.root)


// 뷰어댑터
class MyPagerAdapter(val datas: MutableList<String>): RecyclerView.Adapter<RecyclerView.ViewHolder>(){
    
    override fun getItemCount(): Int = datas.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
        MyPagerViewHolder(ItemMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val binding = (holder as MyPagerViewHolder).binding
        binding.itemText.text = datas[position]

        when(position % 3) {
            0 -> binding.itemRoot.setBackgroundColor(Color.RED)
            1 -> binding.itemRoot.setBackgroundColor(Color.BLUE)
            2 -> binding.itemRoot.setBackgroundColor(Color.GREEN)
        }
    }
}

 

 

 

마지막으로 이 어댑터를 뷰페이저2에 적용합니다.

// MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val datas = mutableListOf<String>()

        for(i in 1..3){
            datas.add("item $i")
        }

        // 뷰페이저2 어댑터에 MyPagerAdapter 적용
        binding.viewpager.adapter = MyPagerAdapter(datas)

    }
}

 

 

실행 결과

 

 


 

프래그먼트 어댑터를 이용한 뷰페이저2 구현

 

RecyclerView.Adapter를 이용해 뷰페이저2를 구현할 수도 있지만 대부분 각 항목(화면)은 복잡하게 작성되며 각각의 화면마다 구성이 다릅니다. 따라서 각 항목의 내용은 보통 프래그먼트로 작성합니다. 항목을 프래그먼트로 작성했으면 FragmentStateAdapter로 뷰페이저2를 구현합니다.

 

// 뷰페이저2 구현 - 프래그먼트 어댑터 이용

class MyFragmentPagerAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
    val fragments = listOf<Fragment>(OneFragment(), TwoFragment(), ThreeFragment())
    
    override fun getItemCount(): Int = fragments.size
    override fun createFragment(position: Int): Fragment = fragments[position]
}

 

FragmentStateAdapter를 상속받아 어댑터를 작성하며 getItemCount()와 createFragment() 함수를 재정의한 것입니다. getItemCount() 함수는 항목(화면)의 개수를 구하고 createFragment() 함수는 항목을 구성하는 프래그먼트 객체를 얻습니다. 그리고 createFragment() 함수에서 반환하는 프래그먼트 객체가 각 항목에 출력됩니다. 이를 위해서는 각 화면마다의 프래그먼트를 미리 만들어둬야 합니다.

 

 

 

마지막으로 이 어댑터를 뷰페이저2에 적용합니다. activity_main.xml 파일의 내용은 동일합니다.

// MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 뷰페이저2 어댑터에 MyFragmentPagerAdapter 적용
        binding.viewpager.adapter = MyFragmentPagerAdapter(this)

    }
}

 

 

실행 결과

 

 


 

뷰페이저2에서는 화면 스와이프 방향도 설정할 수 있습니다. 기본으로 가로 방향이 적용되는데, 만약 세로 스와이프로 뷰페이저2를 구성하고 싶다면 orientation 속성값을 다음처럼 설정합니다.

// 뷰페이저2를 세로로 적용

binding.viewpager.orientation = ViewPager2.ORIENTATION_VERTICAL

 

 

 

 

 

 

ImageView(이미지뷰)를 보여주는 방법

 

안드로이드에서 ImageView(이미지뷰)를 만든 후, 이미지를 코드 상에서 보여주는 여러가지 방법이 있지만 대표적인 3가지 방법을 알아보겠습니다.

 

 

1. 프로젝트 내 res/drawable 디렉토리에 있는 파일을 보여주는 방법

    ImageView.setImageResource(int resourceId)

 

2. Drawable 객체를 이용한 방법

    ImageView.setImageDrawable(Drawable drawable)

 

3. Bitmap 객체를 이용한 방법

    ImageView.setImgeBitmap(Bitmap bitmap)

 

 

 

MainActivity.kt

 

// 임의로 준비한 이미지 small.png를 res/drawable 디렉토리로 저장했습니다.
// 가독성을 위해 MainActivity.kt 코드 일부와
// 각각의 이미지뷰 id인 img* 변수 앞 뷰바인딩 객체는 생략하였으므로 실제 사용 시 주의해주세요.

// res/drawable 디렉토리
img1.setImageResource(R.drawable.small)

// Drawable 객체 이용
val drawable = getDrawable(R.drawable.small)
img2.setImageDrawable(drawable)

// Bitmap 객체 이용
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.small)
img3.setImageBitmap(bitmap)

 

 

 

 

 

 

 

List와 MutableList

코틀린에서는 컬렉션(Collection)을 크게 두 가지로 구분하여 정의하는데, 바로 immutable(불변)과 mutable(가변)입니다. List는 불변 컬렉션이고 MutableList는 가변 컬렉션입니다.

 

사전적 의미처럼 immutable은 'read only'을 의미하지만, mutable은 'read & write'을 의미합니다. 따라서 List형은 리스트의 변화가 없어야 할 때 주로 사용하고, MutableList형은 원소의 추가/삭제 등 리스트의 변경이 필요한 경우에 사용합니다. 그래서 List에는 MutableList의 add()나 remove()/removeAt()과 같은 함수가 정의되어있지 않습니다.

 

 

 

활용 코드

fun main(){

    val list = listOf(1, 2, 3, 4, 5)
    // list.add(10)             // 오류. add() 함수 없음.
    println("\n====================[list]====================")
    println(list)

    
    val mList = mutableListOf(1, 2, 3, 4, 5)
    println("\n====================[mList]====================")
    println(mList)

    
    mList.addAll(listOf(10, 20, 30))
    mList.add(99)
    mList.add(3, -10)
    println("\n====================[mList]====================")
    println(mList)
    

    if (mList.remove(100)) {
        println("\n100이 삭제되었습니다.")
    } else {
        println("\n100이 존재하지 않습니다.")
    }
}

 

 

실행 결과

 

 

 

 

코틀린 컬렉션 내부에는 indices라는 프로퍼티가 정의되어 있는데, 이는 인덱스 범위를 나타냅니다. 이 indices 속성은 반복문을 통한 컬렉션 처리 시에 조금 더 편리하게 만들어줍니다. indices 속성은 immutable과 mutable에 구애받지 않습니다.

// indices 속성을 이용한 반복문

for (i in list.indices) {
    print("${list[i] + 100}\t")
}

// 출력 결과 : 101  102  103  104  105

'📌 Kotlin' 카테고리의 다른 글

[Kotlin] 널 안정성  (0) 2023.02.12

 

 

 

 

 

알림 채널 API 이해

 

상태 바는 화면 상단의 배터리, 네트워크, 시간 등 시스템의 상태 정보가 출력됩니다. 이 상태 바에 앱의 정보를 출력하는 것을 알림(notification)이라고 합니다.

 

원래 상태 바는 시스템에서 관리하는 곳이며 앱이 직접 제어할 수 없습니다. 그런데 앱에서 시스템에 의뢰하면 시스템에서 관리하는 상태 바에 앱의 알림을 출력할 수 있습니다. 따라서 앱의 화면을 구성하거나 사용자 이벤트를 처리하는 프로그래밍과는 구조가 다르며 알림을 위해 제공하는 API를 이용해야 합니다.

 

 

 

 

 

 

알림은 NotificationManager의 notify() 함수로 발생합니다. notify() 함수에는 NotifiicationCompat.Builder가 만들어 주는 Notification 객체를 대입하며 이 객체에 알림 정보가 저장됩니다. 그런데 NotificationCompat.Builder를 만들 때 NotificationChannel 정보를 대입해 줘야 합니다.

 

 

정리하자면 NotificationChannel로 알림 채널을 만들고 NotificationManager에 등록합니다. 만들어진 채널 정보를 대입해 NoificationCompat.Builder를 만든 후, 이 빌더로 Notification 객체를 만듭니다. 최종적으로 이 Notification 객체를 NotificationManager의 notify() 함수에 대입하면 알림이 표시됩니다.

 


 

알림 빌더

 

Notification을 만들기 위해서는 NotificationCompat.Builer가 필요한데 빌더를 만드는 방법이 API 레벨 26(Android 8 오레오) 버전부터 변경됐습니다. 26 버전 이전까지는 다음 생성자를 이용합니다.

  • Builder(context: Context!)

 

 

하지만 26 버전 이상부터는 빌더를 만들 때 NotificationChannel을 만들고 이 채널의 식별값(채널 아이디)을 빌더의 생성자 매개변수에 지정해 줘야 합니다.

  • Builder(context: Context!, channelId: String!)

 

 

알림 채널 생성자는 다음과 같습니다.

  • NotificationChannel(id: String!, name: CharSequence!, importance: Int)

 

채널의 식별값과 설정 화면에 표시할 채널 이름을 문자열로 지정합니다. 세 번째 매개변수는 이 채널에서 발생하는 알림의 중요도이며 다음의 상수로 지정합니다.

중요도 상수 설명
NotificationManager.IMPORTANCE_HIGH 긴급 상황으로 알림음이 울리며 헤드업으로 표시
NotificationManager.IMPORTANCE_DEFAULT 높은 중요도이며 알림음이 울림
NotificationManager.IMPORTANCE_LOW 중간 중요도이며 알림음이 울리지 않음
NotificationManager.IMPORTANCE_MIN 낮은 중요도이며 알림음도 없고 상태 바에도 표시되지 않음

 

 

채널의 각종 정보는 함수나 프로퍼티로 설정할 수 있습니다.

  • fun setDescription(description: String!): Unit → 채널의 설명 문자열
  • fun setShowBadge(showBadge: Boolean): Unit → 홈 화면의 아이콘에 배지 아이콘 출력 여부
  • fun setSound(sound: Uri!, audioAttributes: AudioAttributes!): Unit → 알림음 재생
  • fun enableLights(lights: Boolean): Unit → 불빛 표시 여부
  • fun setLightColor(argb: Int): Unit → 불빛이 표시된다면 불빛의 색상
  • fun enableVibration(vibration: Boolean): Unit → 진동을 울릴지 여부
  • fun setVibrationPattern(vibrationPattern: LongArray!): Unit → 진동을 울린다면 진동의 패턴 설정

 

 

이러한 내용을 반영하여 알림 빌더(NotificationCompat.Builder)를 다음처럼 작성할 수 있습니다.

// 알림 빌더 작성 코드

val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val builder: NotificationCompat.Builder

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
	val channelId = "first-channel"
    val channelName = "My first channel"
    val channel = NotificationChannel(
    	channelId,
        channelName,
        NotificationManager.IMPORTANCE_HIGH
    ).apply {
    	description = "My first channel description"
    }
    
    // 채널에 다양한 정보 설정 (작성하지 않아도 알림 표시에 지장은 없음)
    channel.showBadge(true)
    channel.enableLights(true)
    channel.lightColor = Color.RED
    channel.enableVibration(true)
    channel.vibrationPattern = longArrayOf(100, 200, 100, 200)
    
    // 채널을 NotificationManager에 등록
    manager.createNotificationChannel(channel)
    
    // 채널을 이용해 빌더 생성 (API 레벨 26 오레오 버전 이상부터)
    builder = NotificationCompat.Builder(this, channelId)
} else { // 26 이전 버전에서의 빌더 생성
    builder = NotificationCompat.Builder(this)
}

 


 

알림 객체

 

알림 빌더(NotificationCompat.Builder)를 만들었으면 이 빌더를 이용해 Notification 객체를 만듭니다. 알림은 스몰 아이콘과 발생 시각, 제목, 내용 등으로 구성됩니다. 이러한 알림 정보를 빌더의 세터 함수를 이용해 설정해야 합니다.

// 알림 객체 설정

builder.setSmallIcon(android.R.drawble.ic_notification_overlay)
builder.setWhen(System.currentTimeMillis())
builder.setContentTitle("Test title")
builder.setContentText("Test text")

 

 

 

여기까지 작성했다면 이제 NotificationManager 클래스의 notify() 함수를 이용해 알림을 띄웁니다. 

// 알림 발생

manager.notify(12, builder.build())

 

알림 발생 결과

 

 

 

builder.build() 함수가 Notification 객체를 만들어 반환하고, 이로써 알림이 발생합니다. notify() 함수의 첫 번째 매개변숫값은 알림을 식별하는 데 사용하는 숫자이며 개발자가 임의로 지정합니다. 이 식별값은 사용자 폰에 발생한 알림을 코드에서 취소할 때 사용합니다. 이때 cancel() 함수를 이용하며 매개변수로 취소할 알림의 식별값을 전달합니다.

// 알림 취소

manager.cancel(12)

 

 

 

사용자가 알림을 터치하면 이벤트가 발생할 수 있으며 알림은 화면에서 자동으로 사라집니다. 또한 사용자가 알림을 손가락으로 밀어서(스와이프) 취소할 수 있습니다. 터치나 스와이프를 하더라도 알림이 사라지지 않게 하려면 빌더의 세터 함수로 지정해야 합니다.

// 알림 취소 막기

builder.setAutoCancel(false)
builder.setOngoing(true)

 

setAutoCancel(false)로 지정하면 알림을 터치할 때 이벤트는 발생하지만 알림이 사라지지는 않습니다. 또한 setOngoing(true)로 지정하면 사용자가 알림을 스와이프해도 사라지지 않습니다. 따라서 2가지를 모두 설정했다면 사용자가 알림을 취소할 수 없으며 결국 코드에서 특정 순간에 cancel() 함수로 취소해야 합니다.

 

 

 

 

지금까지 알림 채널을 이용한 알림 빌더 생성과 알림 객체 생성, 마지막으로 기본적인 알림을 띄우는 방법까지 알아봤습니다. 알림을 터치할 때 발생하는 이벤트는 대부분 앱의 액티비티 화면을 실행하는 것입니다. 그런데 알림은 앱이 관할하는 화면이 아니며 시스템에서 관리하는 상태 바에 출력하는 정보입니다. 그러므로 이 알림에서 발생한 터치 이벤트는 앱의 터치 이벤트로 처리할 수 없고, 이벤트가 발생하는 시점을 앱에서 정할 수 없으므로 인텐트(intent)를 이용해야 합니다. 인텐트를 이용한 알림 터치 이벤트 처리는 이어지는 글에서 알아보겠습니다.

 

 

 

 

 

안드로이드 에뮬레이터 무한 로딩 / 에뮬레이터 is already running 오류

 

 

① AVD Manager 에뮬레이터 실행 버튼을 눌렀을 때

 

② 좌 : 작업 관리자 / 우 : 안드로이드 스튜디오 에뮬레이터 창

 

 

어제까지 잘 사용하던 안드로이드 스튜디오 에뮬레이터에 갑자기 문제가 생겼습니다.

 

 

저의 경우는 안드로이드 스튜디오를 종료했다가 다시 실행하거나, 로컬 프로젝트를 열었을 때마다 오류가 발생하는 것 같습니다. 에뮬레이터를 실행하면 Starting AVD...를 진행하고 완료가 되었는데도 불구하고 에뮬레이터가 뜨지 않는 문제입니다. 실제로 AVD(Android Virtual Device) Manager와 백그라운드에서는 에뮬레이터가 돌아가고 있었습니다.

 

 

②번 사진에서 작업 관리자의 qmenu-system-x86_64.exeemulator64-crash-service.exeemulator.exe가 에뮬레이터 관련 프로세스입니다. 보시다시피 작업 관리자에서는 동작 중이지만 오른쪽 안드로이드 스튜디오 에뮬레이터 창에서는 현재 동작하는 에뮬레이터가 없다고 뜹니다.

 

 

 

 

③ C:\ANDROID_AVD_HOME/Pixel_2_API_30.avd 아래 .lock 폴더

 

 

①번 사진에 나와있는 것처럼 파일 탐색기로 [C:\ANDROID_AVD_HOME/Pixel_2_API_30.avd] 경로를 가보면 hardware-qemu.ini.lock 폴더(③번 사진)가 생성되어 있습니다. 이 폴더를 삭제하면 에뮬레이터가 다시 동작하긴 하지만 정보가 초기화되고 첫 부팅시 꽤 오랜 시간이 걸립니다.

 

 

따라서 초기화 과정이 불필요하다면 작업 관리자에서 단순히 에뮬레이터를 강제 종료시키고 재실행하는 것을 추천드립니다. 만약 이 방법으로도 안 된다면 .lock 폴더를 삭제해야 합니다.

작업 관리자에서의 에뮬레이터 종료는 [qmenu-system-x86_64.exe - 마우스 오른쪽 버튼 - 작업 끝내기] 입니다.

 

 

 

 

원인은 잘 모르겠지만 당분간은 불편하더라도 이런 식으로 사용해야 될 것 같습니다.

 

 

 

 

 

문제

수행해야 할 작업 N개 (3 ≤ N ≤ 10000)가 있다. 각각의 작업마다 걸리는 시간(1 ≤ 시간 ≤ 100)이 정수로 주어진다.

 

몇몇 작업들 사이에는 선행 관계라는 게 있어서, 어떤 작업을 수행하기 위해 반드시 먼저 완료되어야 할 작업들이 있다. 이 작업들은 번호가 아주 예쁘게 매겨져 있어서, K번 작업에 대해 선행 관계에 있는(즉, K번 작업을 시작하기 전에 반드시 먼저 완료되어야 하는) 작업들의 번호는 모두 1 이상 (K-1) 이하이다. 작업들 중에는, 그것에 대해 선행 관계에 있는 작업이 하나도 없는 작업이 반드시 하나 이상 존재한다. (1번 작업이 항상 그러하다)

 

모든 작업을 완료하기 위해 필요한 최소 시간을 구하여라. 물론, 서로 선행 관계가 없는 작업들은 동시에 수행 가능하다.

 

 

 

입력

 

첫째 줄에 N이 주어진다.

두 번째 줄부터 N+1번째 줄까지 N개의 줄이 주어진다. 2번째 줄은 1번 작업, 3번째 줄은 2번 작업, ..., N+1번째 줄은 N번 작업을 각각 나타낸다. 각 줄의 처음에는, 해당 작업에 걸리는 시간이 먼저 주어지고, 그 다음에 그 작업에 대해 선행 관계에 있는 작업들의 개수(0 ≤ 개수 ≤ 100)가 주어진다. 그리고 선행 관계에 있는 작업들의 번호가 주어진다.

 

 

 

출력

 

첫째 줄에 모든 작업을 완료하기 위한 최소 시간을 출력한다.

 

 


 

 

문제 해결

 

문제에서 K번 작업의 선행 작업들의 번호는 모두 1이상 K-1 이하라는 조건이 주어졌으므로 dynamic programming 기법을 사용할 수 있습니다. 해결 알고리즘은 간단합니다. K번 작업의 선행 작업들 중 가장 마지막까지 수행된 작업의 시간에 K번의 작업시간을 더해줍니다.

 

 

각 작업이 걸리는 시간을 저장하는 배열을 arr_time[],

dp[i]에 i번 작업이 완료되는 데 걸리는 최소 시간,

task[i][]에 i번 작업의 선행 작업

을 저장하겠습니다.

 

 

한 가지 주의사항이 있습니다. 문제를 잘 읽어보면 선행 작업이 없는 작업은 최소 한 개 이상이며 1번이 그러하다고 적혀있습니다. r을 i번의 선행 작업 번호라고 한다면, dp[i] = max(dp[i], dp[r] + arr_time[i]) 처럼 작성하면 선행 작업이 없는 작업일 때는 이 명령을 수행하지 않으므로 엉뚱한 결과가 나올 수 있습니다. 

 

 

다음과 같은 입력이 주어졌다고 가정하겠습니다.

5

5 0

3 1 1

3 1 2

3 1 3

100 0

 

위 상황은 5개의 작업 중 1번부터 4번까지는 (1 - 2 - 3 - 4)로 연쇄적이고, 5번은 독립적입니다.

 

 

dp[i] = max(dp[i], dp[r] + arr_time[i])로 수행한 결과는 잘못된 값인 14가 나옵니다. 이유는 마지막 입력인 5번 작업은 선행 작업이 없으므로 max 문장이 수행되지 않기 때문입니다. 옳은 결과인 100을 출력하려면 선행 작업이 없는 경우를 대비하여, 선행 작업의 시간이 0 또한 가능하게 만들어줍니다.

 

 

 

코드

#include <iostream>
#include <vector>
using namespace std;

int n, m, ans, arr_time[10001], dp[10001];
vector<int> task[10001];

int main() {
	cin >> n;

	for (int i = 1; i <= n; i++) {
		cin >> arr_time[i] >> m;

		while (m--) {
			int pretask;
			cin >> pretask;
			task[i].push_back(pretask);
		}
	}

	dp[1] = arr_time[1];

	for (int i = 2; i <= n; i++) {
		int max_pretask = 0;

		for (auto r : task[i])
			max_pretask = max(max_pretask, dp[r]);

		dp[i] = max_pretask + arr_time[i];

		ans = max(ans, dp[i]);
	}

	cout << ans;
}

'🎲 BOJ > 🥈' 카테고리의 다른 글

[C++] 백준 2477 : 참외밭  (0) 2023.02.04
[C++] 백준 10826 : 피보나치 수 4  (0) 2022.02.06
[C++] 백준 1181 : 단어 정렬  (0) 2022.02.04
[C++] 백준 9084 : 동전  (0) 2022.02.03

 

 

 

LayoutInflater 이해하기

 

다이얼로그를 만들다 보면 개발 툴에서 제공하지 않는, 개발자가 원하는 형태의 창을 구성하고 싶을 때가 있습니다. 이를 '커스텀 다이얼로그'라고 합니다. 커스텀 다이얼로그도 AlertDialog를 이용합니다.

 

커스텀 다이얼로그가 어떻게 만들어지는지 공부하기 전에 LayoutInflater라는 클래스를 이해해야 합니다. 이 클래스는 커스텀 다이얼로그뿐만 아니라 매우 다양한 곳에서 이용되므로 꼭 알고 있어야 합니다.

 

LayoutInflater(레이아웃 전개자) 클래스는 레이아웃 XML 파일을 코드에서 초기화(흔히 전개라고 표현)하는 기능을 제공합니다. 즉, XML 파일은 단순히 디자인 정보가 담긴 텍스트일 뿐이며, 결국 화면에서 보이려면 XML에 선언한 대로 실제 뷰 객체를 생성해서 메모리에 올려야 합니다. 그리고 이 작업을 LayoutInflater가 해줍니다.

 

 

 

Q. 화면 구성 시 setContentView() 함수를 사용하는 것과 LayoutInflater 객체를 사용하는 것의 차이?

 

A. 액티비티 XML 파일을 작성하면 setContentView() 함수에서 액티비티가 포함한 UI 구성요소들을 메모리에 할당하게 됩니다. 이 때 setContentView() 함수는 내부적으로 LayoutInflater 객체를 참조합니다.

 

이런 면에서 setContentView() 함수와 LayoutInflater의 기능은 같지만 사용하는 목적이 다릅니다. 액티비티 UI가 이미 생성된 이후에 추가적인 UI 요소들을 동적으로 생성해야할 경우에는 LayoutInflater 객체를 사용해야 합니다. 액티비티는 액티비티 객체가 생성될 때 액티비티 UI도 생성하므로 생성 시점이 명확한 반면, 언제 생성해야 할 지 정확한 시점을 알 수 없는 UI 요소들은 inflater로 동적 생성합니다.

 

즉, 액티비티의 화면을 구성하는 레이아웃 XML 파일이라면 LayoutInflater가 아니라 setContentView() 함수를 이용하면 됩니다. 하지만 커스텀 다이얼로그, 리스트 뷰, 리사이클러 뷰의 항목 화면, 프래그먼트를 위한 XML 파일 등 액티비티의 화면을 목적으로 하지 않는 레이아웃 XML 파일은 LayoutInflater를 이용합니다.

 


 

 

XML 파일 초기화하기

 

LayoutInflater로 레이아웃 XML 파일을 초기화하는 작업은 두 가지 방법이 있습니다. 뷰 바인딩을 사용하는 방법과 그렇지 않은 방법입니다. 먼저 뷰 바인딩을 사용하지 않고 XML 파일을 전개하는 방법입니다.

 

1. 뷰 바인딩을 사용하지 않은 XML 파일 초기화

// 뷰 바인딩을 사용하지 않은 XML 파일 초기화

val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

val rootView = inflater.inflate(R.layout.activity_tmp, null)

 

우선 getSystemService() 함수로 LayoutInflater를 얻습니다. 그리고 inflate() 함수를 호출하면서 초기화할 레이아웃 XML 파일 정보를 매개변수로 전달합니다. 위 코드의 activity_tmp는 임의로 설정한 XML 파일 이름입니다.

 

inflate() 함수의 반환값은초기화된 XML의 루트 태그에 해당하는 객체입니다. 만약 XML 파일의 루트 태그가 <LinearLayout>이라면 LinearLayout 객체를 반환합니다.

 

 

 

2. 뷰 바인딩을 사용한 XML 파일 초기화

 

다음은 뷰 바인딩을 적용한 XML 파일 전개 방법입니다. 뷰 바인딩 기법을 이용한다면 XML 초기화 코드를 조금 더 간단하게 작성할 수 있습니다.

// 뷰 바인딩을 적용한 XML 파일 초기화

val binding = ActivityTmpBinding.inflate(layoutInflater)

val rootView = binding.root

 

초기화할 XML에 해당하는 바인딩 클래스의 inflate() 함수를 호출하면서 매개변수로 layoutInflater 객체를 전달만 해주면 알아서 초기화되고 루트 뷰 객체를 얻을 수 있습니다.

 

 


 

 

커스텀 다이얼로그 만들기

 

커스텀 다이얼로그를 만들기 위해서는 먼저 다이얼로그를 구성하는 레이아웃 XML 파일을 작성해야 합니다. res/layout 폴더에 dialog_custom.xml 파일을 다음처럼 작성했다고 가정하겠습니다.

// dialog_custom.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Input text" />
        
    <RadioGroup
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="male" />
            
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="female" />
    </RadioGroup>
    
</LinearLayout>

 

 

 

이제 이 dialog_custom.xml 파일을 LayoutInflater로 초기화해서 다이얼로그에 적용하면 됩니다. AlertDialog의 setView() 함수에 매개변수로 해당 뷰 객체를 전달하면 창의 내용 영역에 출력됩니다.

// 커스텀 다이얼로그 출력

val dialogBinding = DialogCustomBinding.inflate(layoutInflater)

AlertDialog.Builder(this).run {
    setTitle("Custom Dialog")
    setView(dialogBinding.root)
    setPositiveButton("닫기", null)
    show()
}

 

 

출력 결과

 

 

 

 

안드로이드 다이얼로그의 기본은 알림 창으로 부르는 AlertDialog입니다. 알림 창은 간단한 메시지 뿐만 아니라 다양한 화면도 출력할 수 있습니다. 알림 창은 제목, 내용, 버튼 영역 크게 세 가지로 구분됩니다. 그런데 이 3가지 영역이 항상 보이는 것은 아닙니다. 알림 창 설정 시 제목과 버튼 정보를 지정하지 않았다면 내용 영역만 나오게 됩니다.

 

알림 창의 생성자는 접근 제한자가 protected로 선언되어 직접적인 객체 생성이 불가능합니다. 대신 AlertDialog.Builder를 제공하므로 이 빌더를 이용해 알림 창을 만듭니다. 먼저 AlertDialog.Builder를 생성하고 빌더의 세터 함수로 알림창의 정보를 지정합니다.

 

 

 

AlertDialog 생성하기

 

// AlertDialog 생성

AlertDialog.Builder(context: Context!)

 

 

 

다음은 알림 창에 아이콘에 제목, 내용을 지정하는 빌더의 세터 함수입니다.

// 제목 영역에 아이콘 출력
open fun setIcon(iconId: Int): AlertDialog.Builder!


// 제목 문자열 출력
open fun setTitle(title: CharSequence!): AlertDialog.Builder!


// 내용 영역에 간단한 문자열 출력
open fun setMessage(message: CharSequence!): AlertDialog.Builder!

 

 

 

다음은 알림 창에 버튼을 지정하는 함수입니다.

open fun setPositiveButton(text: CharSequence!, listener: DialogInterface.OnClickListener!): AlertDialog.Builder!


open fun setNegativeButton(text: CharSequence!, listener: DialogInterface.OnClickListener!): AlertDialog.Builder!


open fun setNeutralButton(text: CharSequence!, listener: DialogInterface.OnClicklistener!): AlertDialog.Builder!

 

 

각 함수의 첫 번째 매개변수는 버튼의 문자열, 두 번째 매개변수는 버튼을 클릭했을 때 처리할 이벤트 핸들러입니다. 만약 버튼을 클릭했을 때 처리할 내용이 없다면 두 번째 매개변수에 null을 대입합니다. null을 대입할 경우 버튼을 클릭하면 알림 창이 닫힙니다. 알림 창의 버튼은 최대 3개까지 추가 가능하며, 만약 같은 함수를 여러 번 사용하면 마지막 함수의 버튼으로 수행됩니다.

 

// 알림 창 띄우기

AlertDialog.Builder(this).run {
    setTitle("test dialog")
    setIcon(android.R.drawable.ic_dialog_info)
    setMessage("test message")
    setPositiveButton("YES", null)
    setNegativeButton("NO", null)
    setNeutralButton("MORE", null)
    show()
}

 

 

알림창 띄우기 코드 실행 결과

 


 

버튼의 이벤트 핸들러 등록

 

// 버튼의 이벤트 핸들러 등록

val eventHandler = object: DialogInterface.OnClickListener {
    override fun onClick(p0: DialogInterface?, p1: Int) {
        if (p1 == DialogInterface.BUTTON_POSITIVE){
            Log.d("ohsopp", "YES Button")
        }
        else if (p1 == DialogInterface.BUTTON_NEGATIVE) {
            Log.d("ohsopp", "NO Button")
        }
        else if (p1 == DialogInterface.BUTTON_NEUTRAL) {
            Log.d("ohspop", "MORE Button")
        }
    }
}

 

 

 

 

위의 이벤트 핸들러를 사용할 버튼의 세터 함수의 두 번째 매개변수에 넣어줍니다.

setPositiveButton("YES", eventHandler)


setNegativeButton("NO", eventHandler)


setNeutralButton("MORE", eventHandler)

 


 

목록을 출력하는 알림 창

 

 

알림 창의 내용 영역에는 간단한 문자열을 출력하는 setMessage() 말고도 다양한 함수가 있습니다. 만약 목록을 제공하고 이 중 하나를 선택받는 알림 창을 만들고자 한다면 setItems(), setMultiChoiceItems(), setSingleChoiceItems() 함수를 이용합니다. 함수에서 첫 번째 매개변수는 배열 정보이며 이 배열의 문자열이 목록에 출력됩니다.

주의할 점은 setMessage() 함수를 호출하는 경우 setMessage() 함수가 우선 순위를 가지게 됩니다.

// 1. 목록을 출력하는 알림 창 : setItems()

val items = arrayOf<String>("apple", "banana", "peach", "lemon", "orange")

AlertDialog.Builder(this).run {
    setTitle("items test")
    setIcon(android.R.drawable.ic_dialog_info)
    setItems(items, object: DialogInterface.OnClickListener {
    	override fun onClick(p0: DialogInterface?, p1: Int) {
        	Log.d("ohsopp", "Selected fruit : ${items[p1]}")
        }
    })
    setPositiveButton("OK", null)
    show()
}

 

 

실행 결과

 

 

setItems() 함수의 두 번째 매개변수는 항목을 선택할 때의 이벤트 핸들러이며 사용자가 항목을 선택하면 onClick() 함수가 자동으로 호출됩니다. 선택한 항목의 인덱스는 onClick() 함수의 두 번째 매개변수 p1으로 전달됩니다. 또한 아래처럼 이벤트 핸들러를 변수로 선언할 수도 있습니다.

// 이벤트 핸들러를 변수로 선언

val itemEventHandler = object: DialogInterface.OnClickListener {
    override fun onClick(p0: DialogInterface?, p1: Int) {
    	Log.d("ohsopp", "Selected fruit : ${items[p1]}")
    }
}


setItems(items, itemEventHandler)

 


 

체크박스를 포함하는 목록을 출력하는 알림 창

 

 

setMultiChoiceItems() 함수는 다중 선택을 위한 체크박스가 함께 출력되는 항목을 만들어 줍니다.

두 번째 매개변수로 처음 체크 상태를 지정합니다.

// 2. 체크박스를 포함하는 목록 알림 창 : setMultiChoiceItems()

setMultiChoiceItems(items, booleanArrayOf(true, false, true, true, false), object: DialogInterface.OnMultiChoiceClickListener {
    override fun onClick(p0: DialogInterface?, p1: Int, p2: Boolean) {
    	Log.d("ohsopp", "${items[p1]} is ${if(p2) "selected" else "released"}")
    }
})

 

 

실행 결과

 


 

라디오 버튼을 포함하는 목록을 출력하는 알림 창

 

 

setSingleChoiceItems() 함수는 하나만 선택할 수 있는 라디오 버튼으로 구성된 항목을 만들어줍니다.

두 번째 매개변수로 처음 선택할 항목을 지정합니다.

// 3. 라디오 버튼을 포함하는 목록 알림 창 : setSingleChoiceItems()

setSingleChoiceItems(items, 2, object: DialogInterface.OnClickListener {
    override fun onClick(p0: DialogInterface?, p1: Int) {
    	Log.d("ohsopp", "${items[p1]} is selected")
    }
})

 

 

실행 결과

 


 

이외의 함수들

 

 

알림 창의 제목, 내용, 버튼을 구성하는 함수 외에 속성을 설정하는 함수를 사용할 수 있습니다.

open fun setCancelable(cancelable: Boolean): AlertDialog.Builder!


open fun setCanceledOnTouchOutside(cancel: Boolean): Unit

 

 

두 함수 모두 사용자의 행동에 따라 알림 창을 닫을지를 설정합니다. setCancelable() 함수는 사용자가 기기의 뒤로가기 버튼을 눌렀을 때, setCanceledOnTouchOutside() 함수는 알림 창의 바깥 영역을 터치했을 때 매개변수가 true이면 닫고 false이면 닫지 않습니다. 기본값은 true입니다.

AlertDialog.Builder(this).run {

    //...(생략)...

    setCancelable(false)
    show()
}.setCanceledOnTouchOutside(false)

 

 

setCancelable() 함수는 AlertDialog.Builder 클래스의 함수이고, setCanceledOnTouchOutside() 함수는 Dialog 클래스의 함수입니다.

 

run()은 매개변수에 람다 함수를 지정할 수 있는 코틀린의 기초 함수입니다. run() 함수에 지정된 람다 함수는 run()을 호출한 객체의 멤버가 되어 람다 함수에서 this는 Builder 객체를 가리킵니다. 그리고 run() 함수의 반환값은 곧 람다 함수의 반환값입니다.

위 소스에서는 run() 함수에 지정한 람다 함수의 마지막 줄에 show() 함수를 호출했습니다.

 

 

안드로이드 공식 문서에서 볼 수 있듯이 show() 함수는 반환값이 AlertDialog 객체이며 이를 run() 함수가 반환하므로

AlertDialog.Builder(this).run { }.setCanceledOnTouchOutside(false) 처럼 작성이 가능합니다.

 

 

 

 

 

문제

홍대병에 걸린 도현이는 겹치는 것을 매우 싫어한다. 특히 수열에서 같은 원소가 여러 개 들어 있는 수열을 싫어한다. 도현이를 위해 같은 원소가 K개 이하로 들어 있는 최장 연속 부분 수열의 길이를 구하려고 한다.

 100,000 이하의 양의 정수로 이루어진 길이가 N인 수열이 주어진다.  이 수열에서 같은 정수를 K개 이하로 포함한 최장 연속 부분 수열의 길이를 구하는 프로그램을 작성해보자.

 

 

 

입력

첫째 줄에 정수 N(1 ≤ N ≤ 200000)과 K(1 ≤ K ≤ 100)가 주어진다.

둘째 줄에는 a1, a2, ... , an이 주어진다. (1 ≤ ai ≤ 100000)

 

 

 

출력

조건을 만족하는 최장 연속 부분 수열의 길이를 출력한다.

 

 


 

 

문제 해결

 

이중 for문을 이용한 브루트포스 알고리즘은 탐색 범위를 나름대로 최적화 후 제출해봤지만 아쉽게도 실행 시간 초과가 발생했습니다.

따라서 투 포인터를 이용하여 해결 가능합니다.

 

start와 end라는 두 개의 포인터가 입력된 수열 ary를 순차적으로 탐색하게 됩니다.

 

end ≤ N를 만족하면 다음 내용을 반복합니다.

1. 먼저 end가 ary의 첫 원소부터 이동합니다.

2. 만약 end가 탐색하는 값 t의 개수가 K개보다 커지면, start는 t의 개수가 K개보다 작거나 같아질 때까지 이동합니다.

3. end - start +1개와 현재 최대값을 비교하여 최대값을 갱신합니다.

 

 

 

코드

#include <iostream>
using namespace std;

int ans, n, k, ary[200001], cnt[100001];

int main() {
	cin >> n >> k;

	for (int i = 1; i <= n; i++) scanf("%d", ary + i);

	int start = 1, end = 0;

	while (end < n) {
		end++;
		cnt[ary[end]]++;

		if (cnt[ary[end]] > k)
			while (cnt[ary[end]] > k) {
				cnt[ary[start]]--;
				start++;
			}
		
		ans = max(ans, end - start + 1);
	}

	cout << ans;
}

'🎲 BOJ > 🥇' 카테고리의 다른 글

[C++] 백준 5430 : AC  (0) 2023.08.06
[C++] 백준 4256 : 트리  (0) 2023.08.06
[C++] 백준 1072 : 게임  (0) 2022.02.08
[C++] 백준 2533 : 사회망 서비스(SNS)  (0) 2022.02.07

+ Recent posts