紫书刷题进行中,题解系列点这里
例题4-5 UVA512 Spreadsheet Tracking
题目大意
给定一个r*c的表格,模拟五种操作,现给出原始单元格坐标,输出最终的单元格坐标,五种操作如下:
- 删除多行/列
- 插入多行/列
- 交换两个单元格
思路分析
有两种思路解决:
- 整体模拟:直接模拟整个表格的变化,得到最终表格,然后直接查询相应单元格
- 单个模拟:将命令序列储存起来,对每一个查询的单元格,模拟执行所有命令,输出结果
前者比较直观,符合思维习惯;后者代码量小,在当前数据规模下效率较高
但二者对于操作的模拟逻辑是一致的,假设当前单元格坐标(x,y),当前操作为op,当前操作对应的行列号为i,伪代码如下:
if op == "DR":
if x == i:
删除(x,y)
else if x > i:
x ++
else if op == "DC":
if y == i:
删除(x,y)
else if y > i:
y ++
else if op == "IR" && x >= i:
x ++
else if op == "IC" && y >= i:
y ++
else if op == "EX":
if (x,y)属于交换单元格中的一个:
x = r
y = c
算法设计
以下详细阐述整体模拟和单个模拟的算法设计思路。
整体模拟
定义Pos结构体,表示单元格坐标,为了在Map中使用Pos做键值,必须重载<
,作为map排序规则
typedef struct Pos{
int x, y;
Pos(int _x=0, int _y=0): x(_x), y(_y){} // 默认值表示默认构造函数
// bool operator < (const Pos& b) const { // map需要排序,必须加const,否则编译报错
// if (x == b.x) return y < b.y;
// else return x < b.x;
// }
friend bool operator < (const Pos& a, const Pos& b) { // 友元函数,map需要排序
if (a.x == b.x) return a.y < b.y;
else return a.x < b.x;
}
} Pos;
定义Map<Pos,Pos>mp1,mp2,mp3
,三者表示含义如下:
- mp1:当前位置->原始位置(始终表示当前表格状态)
- mp2:操作模拟的中间变量(存储表格中间态)
- mp3:原始位置->最终位置(逆置mp1的键值对,便于查询)
为了解决插入或删除多行/列时,行列号小者先执行会影响行列号大的问题,有两种思路:
- 重载<:在结构体中重载运算符<时,定义坐标大者在前,可避免该问题
- 计数:定义
vector<int>cnt(60)
,cnt[i]表示小于等于i的个数。比如当前命令为插入第五行,那么仅需令x+=cnt[5]
,若是删除第五行,仅需令x-=cnt[4]
(删除是严格大于i)
计数实现也简单,分两步:
- 哈希计数:统计当前命令序列每个数字出现的次数,即若当前数字为i,则
cnt[i]++
- 一遍累加:
cnt[i]+=cnt[i-1],i从1开始,cnt[0]=0
单个模拟
定义vector<pair<string,vector<int> > > cmd
,存储当前表格的所有操作命令序列,其中string表示五种操作,vector<int>
表示操作命令对应的行列集合。
在遍历插入/删除集合前,对其进行降序排列,避免小者对大者产生影响
此处实现思路与思路分析中的伪代码一致,不再赘述。
注意点
- 输出时两个表格间需要一个空行,最后一个表格无需多余空行
- 使用结构体做Map的key时,必须重载<,否则无法通过编译,重载可用成员函数/友元函数方式
- 插入或删除多行/列,注意处理行列号的顺序,大者先执行
AC代码(C++11)
整体模拟(Map)
#include<bits/stdc++.h>
using namespace std;
typedef struct Pos{
int x, y;
Pos(int _x=0, int _y=0): x(_x), y(_y){} // 默认值表示默认构造函数
// bool operator < (const Pos& b) const { // map需要排序,必须加const,否则编译报错
// if (x == b.x) return y < b.y;
// else return x < b.x;
// }
friend bool operator < (const Pos& a, const Pos& b) { // map需要排序
if (a.x == b.x) return a.y < b.y;
else return a.x < b.x;
}
} Pos;
map<Pos,Pos> mp1, mp2, mp3; // 当前位置->原始位置,操作模拟的中间变量,原始位置->最终位置
int r, c, op, n, t, num = 0;
string s;
vector<int> cnt(60);
int main() {
while(scanf("%d %d", &r, &c) == 2 && (r != 0 && c != 0)) {
scanf("%d", &op);
if (num != 0) puts("");
printf("Spreadsheet #%d\n", ++num);
mp1.clear(); // 清空
for (int i = 1; i <= r; i ++) { // 初始化:当前位置->原始位置
for (int j = 1; j <= c; j ++) {
mp1.insert({Pos{i,j}, Pos{i,j}});
}
}
while(op --) { // 操作
cin >>s;
if (s != "EX") { // 不为Exchange
cin >>n;
fill(cnt.begin(), cnt.end(), 0); // 初始化
set<int> rcset; // 要删除/插入的行列集合
while(n --) {
cin >>t; rcset.insert(t);
cnt[t] ++; // 统计每个序号出现次数
}
for (int i = 1; i < cnt.size(); i ++) cnt[i] += cnt[i-1]; // cnt[i]表示小于等于i的个数,用于计算最终行列号
for (auto p = mp1.begin(); p != mp1.end(); p ++) { // 遍历每个cell
if (s == "DR") { // 删除行
if (rcset.find(p->first.x) == rcset.end()) // 未找到,不被删除
mp2.insert({{p->first.x - cnt[p->first.x-1],p->first.y}, p->second}); // 插入记录
}
else if (s == "DC") { // 删除列
if (rcset.find(p->first.y) == rcset.end()) // 不被删除
mp2.insert({{p->first.x, p->first.y-cnt[p->first.y-1]}, p->second});
}
else if (s == "IR") { // 插入行
mp2.insert({{p->first.x+cnt[p->first.x], p->first.y}, p->second}); // 更新位置
}
else if (s == "IC") {
mp2.insert({{p->first.x,p->first.y+cnt[p->first.y]}, p->second}); // 更新位置
}
}
mp1 = mp2; mp2.clear(); // 更新当前位置关系
}
else {
int r1,c1,r2,c2;
cin >>r1 >>c1 >>r2 >>c2;
swap(mp1[{r1,c1}], mp1[{r2,c2}]); // 交换单元格,考虑交换单元格包含刚插入的
}
}
mp3.clear();
for (auto p : mp1) mp3.insert({p.second, p.first}); // 逆置:原始位置->最终位置
cin >>n;
int x, y;
while (n --) { // 处理查询
cin >>x >>y;
if (mp3.find({x,y}) == mp3.end()) printf("Cell data in (%d,%d) GONE\n", x, y);
else printf("Cell data in (%d,%d) moved to (%d,%d)\n", x, y, mp3[{x,y}].x, mp3[{x,y}].y);
}
}
return 0;
}
单个模拟
#include<bits/stdc++.h>
using namespace std;
int r, c, op, cnt = 0;
string s;
int main() {
while (scanf("%d %d", &r, &c) == 2 && (r != 0 && c != 0)) {
scanf("%d", &op); // 操作个数
printf("%sSpreadsheet #%d\n", cnt != 1 ? "\n":"", ++cnt); // ++优先级高,控制空行
vector<pair<string,vector<int> > > cmd; // 存储命令和对应的行列集合
while (op --) { // 存储输入命令
cin >>s;
cmd.push_back({s, vector<int>()}); // 初始化
int n, t;
if (s == "EX") n = 4;
else scanf("%d", &n);
while (n --) {
scanf("%d", &t);
cmd.back().second.push_back(t);
}
}
int n, x, y, ansX, ansY;
scanf("%d", &n);
while (n --) { // n个查询
scanf("%d %d", &x, &y);
ansX = x; ansY = y; // 最终结果初始化
for (auto p : cmd) { // 遍历所有命令
if (p.first != "EX") { // 插入/删除
sort(p.second.begin(), p.second.end(), [](int& a, int &b) {return a > b;}); // 降序排列
for (auto i : p.second) { // 遍历删除或插入的行/列号(从大到小,否则前面会影响后面)
if (p.first == "DR") { // 删除行
if (i == ansX) ansX = -1; // -1标记删除
else if (ansX > i) ansX --; // 行号大于i才改变
}
else if (p.first == "DC") { // 删除列,与删除行类似
if (i == ansY) ansY = -1;
else if (ansY > i) ansY --;
}
else if (p.first == "IR" && ansX >= i) ansX ++; // 插入行,>=i就会改变
else if (p.first == "IC" && ansY >= i) ansY ++; // 插入列,>=i就会改变
if (ansX == -1 || ansY == -1) break; // 发现删除,直接结束
}
}
else { // 交换单元格
if (ansX == p.second[0] && ansY == p.second[1]) ansX = p.second[2], ansY = p.second[3];
else if (ansX == p.second[2] && ansY == p.second[3]) ansX = p.second[0], ansY = p.second[1];
}
}
if (ansX == -1 || ansY == -1) printf("Cell data in (%d,%d) GONE\n", x, y);
else printf("Cell data in (%d,%d) moved to (%d,%d)\n", x, y, ansX, ansY);
}
}
return 0;
}