package main

import (
	"fmt"
	"strings"
)

func main() {
	var n int
	var commands string

	fmt.Scanln(&n)
	fmt.Scanln(&commands)

	// 模拟MP3播放器状态
	player := NewMP3Player(n)

	// 执行命令
	for _, cmd := range commands {
		switch cmd {
		case 'U':
			player.MoveUp()
		case 'D':
			player.MoveDown()
		}
	}

	// 输出结果
	player.PrintResult()
}

// MP3Player 模拟MP3播放器
type MP3Player struct {
	totalSongs int // 总歌曲数
	cursor     int // 当前光标位置(1-based)
	pageSize   int // 每页显示歌曲数
	startSong  int // 当前页第一首歌曲
	endSong    int // 当前页最后一首歌曲
}

// NewMP3Player 创建新的MP3播放器
func NewMP3Player(totalSongs int) *MP3Player {
	player := &MP3Player{
		totalSongs: totalSongs,
		cursor:     1,
		pageSize:   4,
		startSong:  1,
		endSong:    min(4, totalSongs),
	}
	return player
}

// MoveUp 向上移动光标
func (p *MP3Player) MoveUp() {
	if p.totalSongs <= p.pageSize {
		// 歌曲总数<=4,不需要翻页
		if p.cursor == 1 {
			p.cursor = p.totalSongs
		} else {
			p.cursor--
		}
	} else {
		// 歌曲总数>4,需要处理翻页
		if p.cursor == p.startSong {
			// 光标在当前页第一首,需要翻页
			if p.startSong == 1 {
				// 当前是第一页,翻到最后一页
				p.startSong = p.totalSongs - p.pageSize + 1
				p.endSong = p.totalSongs
				p.cursor = p.totalSongs
			} else {
				// 翻到上一页
				p.startSong--
				p.endSong--
				p.cursor--
			}
		} else {
			// 光标不在第一首,直接向上移动
			p.cursor--
		}
	}
}

// MoveDown 向下移动光标
func (p *MP3Player) MoveDown() {
	if p.totalSongs <= p.pageSize {
		// 歌曲总数<=4,不需要翻页
		if p.cursor == p.totalSongs {
			p.cursor = 1
		} else {
			p.cursor++
		}
	} else {
		// 歌曲总数>4,需要处理翻页
		if p.cursor == p.endSong {
			// 光标在当前页最后一首,需要翻页
			if p.endSong == p.totalSongs {
				// 当前是最后一页,翻到第一页
				p.startSong = 1
				p.endSong = p.pageSize
				p.cursor = 1
			} else {
				// 翻到下一页
				p.startSong++
				p.endSong++
				p.cursor++
			}
		} else {
			// 光标不在最后一首,直接向下移动
			p.cursor++
		}
	}
}

// PrintResult 打印当前页面和光标位置
func (p *MP3Player) PrintResult() {
	// 输出当前页面显示的歌曲
	var songs []string
	for i := p.startSong; i <= p.endSong; i++ {
		songs = append(songs, fmt.Sprintf("%d", i))
	}
	fmt.Println(strings.Join(songs, " "))

	// 输出当前光标位置
	fmt.Println(p.cursor)
}

// min 返回两个整数中的较小值
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

解题思路

算法分析

这道题的核心是状态机模拟分页逻辑处理。主要涉及:

  1. 状态管理:维护当前页面范围、光标位置
  2. 翻页逻辑:处理特殊翻页和一般翻页
  3. 边界处理:首尾歌曲的循环移动
  4. 命令解析:逐个处理U/D命令

状态机设计

stateDiagram-v2
    [*] --> 初始状态: 歌曲数量n,光标位置1
    初始状态 --> 歌曲数<=4: n <= 4
    初始状态 --> 歌曲数>4: n > 4
    
    歌曲数<=4 --> 向上移动: U命令
    歌曲数<=4 --> 向下移动: D命令
    向上移动 --> 歌曲数<=4: 光标位置更新
    向下移动 --> 歌曲数<=4: 光标位置更新
    
    歌曲数>4 --> 特殊翻页: 边界情况
    歌曲数>4 --> 一般翻页: 非边界情况
    歌曲数>4 --> 光标移动: 非边界情况
    
    特殊翻页 --> 歌曲数>4: 页面范围更新
    一般翻页 --> 歌曲数>4: 页面范围更新
    光标移动 --> 歌曲数>4: 光标位置更新

翻页逻辑详解

flowchart TD
    A[接收U/D命令] --> B{歌曲总数<=4?}
    B -->|是| C[简单循环移动]
    B -->|否| D{光标位置}
    
    C --> E[首尾循环]
    D --> F{光标在页面边界?}
    
    F -->|是| G{是否特殊翻页?}
    F -->|否| H[普通光标移动]
    
    G -->|是| I[特殊翻页逻辑]
    G -->|否| J[一般翻页逻辑]
    
    I --> K[更新页面范围]
    J --> K
    H --> L[更新光标位置]
    K --> L
    E --> L

页面状态管理

graph TD
    A[MP3Player状态] --> B[totalSongs: 总歌曲数]
    A --> C[cursor: 当前光标位置]
    A --> D[pageSize: 每页显示数量]
    A --> E[startSong: 当前页第一首]
    A --> F[endSong: 当前页最后一首]
    
    G[状态更新规则] --> H[光标移动: cursor±1]
    G --> I[页面翻动: startSong±1, endSong±1]
    G --> J[特殊翻页: 首尾跳转]

算法流程图

flowchart TD
    A[读取歌曲数量n] --> B[读取命令字符串]
    B --> C[初始化MP3Player]
    C --> D[遍历每个命令]
    D --> E{命令类型}
    E -->|U| F[执行向上移动]
    E -->|D| G[执行向下移动]
    
    F --> H{歌曲数<=4?}
    G --> I{歌曲数<=4?}
    
    H -->|是| J[简单循环移动]
    H -->|否| K[处理翻页逻辑]
    I -->|是| L[简单循环移动]
    I -->|否| M[处理翻页逻辑]
    
    J --> N[更新光标位置]
    K --> O{光标在边界?}
    L --> N
    M --> P{光标在边界?}
    
    O -->|是| Q{特殊翻页?}
    O -->|否| R[普通移动]
    P -->|是| S{特殊翻页?}
    P -->|否| T[普通移动]
    
    Q -->|是| U[首尾跳转]
    Q -->|否| V[一般翻页]
    S -->|是| U
    S -->|否| V
    
    R --> W[更新状态]
    T --> W
    U --> W
    V --> W
    N --> W
    
    W --> X{还有命令?}
    X -->|是| D
    X -->|否| Y[输出结果]

代码实现思路

  1. 状态管理:使用结构体封装MP3播放器状态维护页面范围(startSong, endSong)和光标位置提供清晰的状态更新接口
  2. 翻页逻辑:区分特殊翻页(首尾跳转)和一般翻页根据光标位置判断是否需要翻页同步更新页面范围和光标位置
  3. 边界处理:歌曲数<=4时的简单循环歌曲数>4时的复杂翻页逻辑正确处理首尾歌曲的跳转

时间复杂度分析

  • 时间复杂度:O(s),其中s是命令长度
  • 空间复杂度:O(1),只使用固定数量的状态变量

关键优化点

  1. 状态封装:使用结构体封装相关状态,提高代码可读性
  2. 逻辑分离:将翻页逻辑和光标移动逻辑分离
  3. 边界处理:正确处理各种边界情况
  4. 代码复用:避免重复代码,提高维护性

边界情况处理

  1. 歌曲数<=4:简单循环,无需翻页
  2. 歌曲数>4: 特殊翻页:首尾跳转一般翻页:正常翻页普通移动:光标在页面中间
  3. 命令为空:直接输出初始状态
  4. 单个命令:正确处理单个U/D命令

测试用例分析

graph TD
    A[测试用例1: 10 UUUU] --> B[验证特殊翻页]
    C[测试用例2: 4 UDUD] --> D[验证简单循环]
    E[测试用例3: 8 UDUD] --> F[验证一般翻页]
    
    B --> G[首尾跳转正确]
    D --> H[循环移动正确]
    F --> I[页面翻动正确]
    
    G --> J[输出格式正确]
    H --> J
    I --> J

算法特点

  1. 状态机设计:清晰的状态转换逻辑
  2. 分页处理:正确处理复杂的分页场景
  3. 边界处理:全面覆盖各种边界情况
  4. 代码结构:面向对象设计,易于扩展

实际应用场景

  1. UI界面设计:分页列表的光标控制
  2. 游戏开发:菜单系统的光标移动
  3. 嵌入式系统:小屏幕设备的分页显示
  4. 移动应用:触摸屏的列表浏览

这个问题的关键在于正确理解翻页逻辑设计清晰的状态管理机制,特别是特殊翻页和一般翻页的区别处理。