UVa 597 Last Name First Please
2026/6/25 11:59:18 网站建设 项目流程

题目描述

给定一组员工的照片文件,文件名格式为FirstName LastName.jpg。现需将文件名改为LastName, FirstName.jpg,即姓氏在前。文件列表按当前文件名(原名或已改名后的新名)的字典序实时更新,且每次改名后,被改名的文件仍保持选中状态。

管理员希望从列表中的第一个文件开始重命名,并且在每次重命名后,只使用一次方向键(上 / 下)来选择下一个待重命名的文件。方向键不会循环(即不会从列表末尾跳转到列表开头)。如果存在这样一条重命名顺序,使得所有文件都能依次被重命名,则输出该顺序下的新文件名;否则输出NO QUICK RENAMING POSSIBLE

输入格式

输入包含多个测试用例,用例之间用一个空行分隔。
每个测试用例包含至多202020行,每行一个文件名。文件名长度不超过323232个字符,名字和姓氏均由字母组成且仅首字母大写。假设“姓氏”为第一个空格与末尾句点之间的部分,且所有姓氏互不相同。
输入以EOF结束。

输出格式

对于每个测试用例,若存在可行重命名顺序,则按该顺序每行输出一个重命名后的文件名(即LastName, FirstName.jpg);否则输出一行NO QUICK RENAMING POSSIBLE。两个连续测试用例的输出之间必须用一个空行隔开。

样例

输入

Christina Peter.jpg Ganesh Ramanarayanan.jpg Laurie Yorr.jpg Lucy Kraus.jpg Melanie Ayala.jpg Nancy Schnell.jpg Ruth Sandweiss.jpg Christina Peter.jpg Ganesh Ramanarayanan.jpg Melanie Ayala.jpg

输出

NO QUICK RENAMING POSSIBLE Peter, Christina.jpg Ayala, Melanie.jpg Ramanarayanan, Ganesh.jpg

(注意:样例输出中第一个用例后有一个空行,这是两个用例输出之间的分隔。)

题目分析

本题本质上是一个状态空间搜索问题。初始时,所有文件名均为原名,列表按原名排序,第一个文件(即字典序最小者)被选中。管理员必须立即重命名该文件(无需方向键)。之后,每一步需要根据当前所有文件的名称(已改名的用新名,未改名的用原名)重新排序,得到新列表;然后,当前选中的文件在列表中的位置已知,我们可以考察它的相邻文件(前一个和后一个)。如果某个相邻文件尚未被重命名,那么我们可以选择它作为下一步,并按下一次方向键完成选择。若两侧相邻文件都已被重命名或不存在,则无法继续。

由于文件数量n≤20n \le 20n20,我们可以用状态压缩表示已经重命名的文件集合,同时记录当前选中的文件编号。每次从当前状态出发,按规则生成所有可能的下一状态,并进行深度优先搜索或广度优先搜索。如果找到一条覆盖所有文件的路径,则输出对应的重命名顺序。

需要特别注意的是:排序依据是文件名的当前显示名称,而不是固定顺序。已重命名的文件会移动到新的字典序位置,可能改变整个列表顺序,从而影响相邻关系。因此,每次状态转移时,必须重新计算当前列表顺序,再确定相邻候选。

另外,初始状态是强制选定第一个文件(原名排序后第一个)并立即重命名,这一步不需要方向键,也不计入方向键次数。我们的搜索从该状态开始。

解题思路

1. 数据表示

将每个文件抽象为一个结构体,包含原始文件名original和重命名后的新文件名newName。文件编号为0∼n−10 \sim n-10n1
对于任意状态,使用一个整数mask表示已重命名文件的集合(第iii位为111表示文件iii已重命名)。当前选中的文件编号记为curId

2. 状态转移

给定状态(mask, curId),需要计算当前列表顺序。为此,创建一个数组order[0..n-1]存储文件编号,然后按如下规则排序:

  • 如果文件iii已重命名(mask & (1<<i)非零),则比较键为files[i].newName
  • 否则比较键为files[i].original

排序后,找到curIdorder中的位置pos。然后,检查pos-1pos+1位置上的文件编号,若存在且尚未重命名,则它们就是合法候选。

将每个候选文件编号nxtId作为下一步的选择,递归调用dfs(mask | (1<<nxtId), nxtId),并在路径中记录nxtId

3. 搜索与剪枝

由于n≤20n \le 20n20,状态总数最多为n⋅2n≈20×106n \cdot 2^n \approx 20 \times 10^6n2n20×106,但实际可达状态远小于此,因为每一步只允许移动到相邻未访问节点,路径长度受限。DFS\texttt{DFS}DFS配合回溯即可在合理时间内完成。

我们还可以加入记忆化(即记录某个状态是否访问过且失败),但本题nnn较小,直接搜索也足够。

4. 初始状态与终止条件

  • 初始状态:将原始文件名排序,第一个文件编号为start,将其重命名,因此mask = 1<<startcurId = start,路径path = {start}
  • 终止条件:若mask == (1<<n)-1,说明所有文件已重命名,搜索成功。

5. 特殊情况

n=1n=1n=1时,无需任何方向键,直接输出该文件的新名即可。

6. 多测试用例输出格式

两个用例之间用空行分隔,注意在输出第一个用例前不要输出多余空行。可以使用一个静态标志变量来控制。

7. 复杂度分析

  • 每个状态需要O(nlog⁡n)O(n \log n)O(nlogn)的时间来排序,以确定相邻关系。状态数最多约为n⋅2nn \cdot 2^nn2n,但实际分支有限,n=20n=20n=20时最坏情况可接受。
  • 空间复杂度为O(n)O(n)O(n)用于路径记录,DFS\texttt{DFS}DFS栈深度为nnn

实际上,由于每一步只能向左或向右移动,路径宽度受限,搜索效率很高。

代码实现

// Last Name First Please// UVa ID: 597// Verdict: Accepted// Submission Date: 2026-06-25// UVa Run Time: 1.810s//// 版权所有(C)2026,邱秋。metaphysis # yeah dot net#include<bits/stdc++.h>usingnamespacestd;structFile{string original;// 原始名称string newName;// 新名称};intn;vector<File>files;// DFS:mask 已重命名集合,curId 当前选中的文件 ID,path 记录重命名顺序booldfs(intmask,intcurId,vector<int>&path){if(mask==(1<<n)-1)returntrue;// 按当前文件名排序,得到列表顺序vector<int>order(n);iota(order.begin(),order.end(),0);sort(order.begin(),order.end(),[&](inta,intb){string nameA=(mask&(1<<a))?files[a].newName:files[a].original;string nameB=(mask&(1<<b))?files[b].newName:files[b].original;returnnameA<nameB;});// 找到当前文件在列表中的位置intpos=find(order.begin(),order.end(),curId)-order.begin();// 收集相邻且未重命名的候选文件vector<int>candidates;if(pos>0){intid=order[pos-1];if(!(mask&(1<<id)))candidates.push_back(id);}if(pos+1<n){intid=order[pos+1];if(!(mask&(1<<id)))candidates.push_back(id);}// 尝试每个候选for(intid:candidates){path.push_back(id);if(dfs(mask|(1<<id),id,path))returntrue;path.pop_back();}returnfalse;}// 处理单个测试用例voidprocessCase(constvector<string>&rawLines){n=(int)rawLines.size();files.resize(n);// 解析每个文件名for(inti=0;i<n;++i){conststring&s=rawLines[i];size_t spacePos=s.find(' ');size_t dotPos=s.rfind('.');string first=s.substr(0,spacePos);string last=s.substr(spacePos+1,dotPos-spacePos-1);string ext=s.substr(dotPos);// 保留扩展名(如 .jpg)files[i].original=s;files[i].newName=last+", "+first+ext;}// 按原始文件名排序(初始列表顺序)sort(files.begin(),files.end(),[](constFile&a,constFile&b){returna.original<b.original;});// 从第一个文件开始重命名(索引 0)vector<int>path;path.push_back(0);intmask=1<<0;boolok=(n==1)?true:dfs(mask,0,path);// 若只有一个文件则直接成功// 输出结果(全局控制用例间空行)staticboolfirstOutput=true;if(!firstOutput)cout<<'\n';firstOutput=false;if(!ok)cout<<"NO QUICK RENAMING POSSIBLE\n";}else{for(intid:path)cout<<files[id].newName<<'\n';}}intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);string line;vector<string>currentLines;while(getline(cin,line)){if(line.empty()){if(!currentLines.empty()){processCase(currentLines);currentLines.clear();}// 忽略多余空行}elsecurrentLines.push_back(line);}// 处理最后一个用例(可能没有结尾空行)if(!currentLines.empty())processCase(currentLines);return0;}

总结

本题的核心在于将“重命名顺序”问题转化为状态空间搜索问题,利用nnn较小的特点,采用状态压缩 + DFS 枚举所有可能的相邻选择路径。需要注意的关键点是:

  • 动态排序:每次状态变化后,列表顺序会因文件名改变而重新调整,因此必须实时计算当前顺序,才能确定相邻文件。
  • 初始条件:题目要求从列表第一个文件开始重命名(无需按键),因此初始状态是固定的,无需遍历所有起点。
  • 可行性:只要有一条路径能覆盖所有节点,即认为可行。搜索过程中通过回溯找到一条路径即可,无需枚举全部。

本题也展示了如何将实际场景抽象为图论中的路径搜索问题,虽然nnn不大,但状态数可能较多,因此合理的设计和剪枝是必要的。代码实现上,采用函数式递归,结构清晰,易于调试。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询