西电ACM竞赛第1001题
问题描述:
最近,亮亮和小W都对苹果很感兴趣!在研究了“最大苹果矩阵”和“给苹果树施肥”的问题后,他们又遇到了一个新的问题:
有一块长n米、宽m米的地,现在小W把地划分成边长1米的小正方形块,共n*m个块。每个块中可能种有一棵苹果树,或放有一个iPhone,也可以什么也没有。然而,亮亮拥有一种超能力,可以将2个宽1米、长度相同的矩形块在空间中直接交换。
亮亮经常对着农场施展超能力,为了不把自己搞晕,他每次总是选择两整行(长度均为m米)或两整列(长度均为n米)进行交换。小W对此十分恼火——当他想拿iPhone打游戏时,却莫名其妙地撞在了苹果树上。小W要求你写一个程序,帮助他确定某一正方形块中放了什么东西。
输入:
输入包含多组数据,请处理到文件结束。
每组数据,第一行2个整数n、m,表示地的尺寸。
之后n行,每行m个英文字母,大写的T表示这里种有苹果树,小写的i表示这里放有iPhone,其他字符表示这里什么也没有。
之后1行,一个整数Q,表示小W询问的次数。
之后Q行,包含3个整数,可能有以下情况:
1 i1 j1 表示小W想知道第i1行第j1列的方块中有什么东西。
2 i1 i2 表示亮亮交换了第i1行与第i2行。
3 j1 j2 表示亮亮交换了第j1列与第j2列。
对于100%的数据,有1<=n*m<=106,1<=Q<=105,1<=i1, i2<=n,1<=j1, j2<=m。
输出:
对于每组数据,输出以一行“Case #x:”开头,x表示数据的编号,从1开始。
对于小W的每次格式为“1 i j”的询问,输出一行。若方块中是苹果树,输出"Tree"。若方块中是iPhone,输出“Phone”。若方块中什么也没有,输出“Empty”。
样例输入:
2 2
Ti
Ti
2
1 1 1
1 2 2
3 3
Tct
Iai
qqT
3
1 1 3
2 1 2
1 1 3
样例输出:
Case #1:
Tree
Phone
Case #2:
Empty
Phone
仔细分析问题不难得出解决此问题需要以下几个步骤:
- 获取场地的长和宽,也就是n(行数),m(列数)
- 将缓存区内场地每个格子的内容存放到内存中,如果开辟一个max{n,m} * max{n,m}的数组有可能会超过内存最大限制128MB,所以怎么存储这些场地数据直接影响到之后的数据处理
- 获取指令的条数Q(包括问题和亮亮的操作)
- 循环Q次依次获取指令,分情况讨论具体的操作
- 结束一次流程,计数器加一,开始处理下一次数据
解决方案
我在思考这个问题的时候得出了两种处理方法:
第一种是利用三元组来存放整个矩阵数据。方法是先用malloc分配一段大小为(m*1)的空间用于存放一行数据,初始化一个三元组链表。利用循环挨个检查每个数据,如果是字符’i’ 或者 字符’T’,申请一个三元组的节点,将该字符所处在的行列号赋值给节点,字符信息也赋值,接入链表,处理完一行数据后,重新往缓存区里接收一行新的数据,重复上述步骤处理,直到所有矩阵中的’i’和’k’字符信息和行列号全部接入链表。这样做的好处是在之后的行交换和列交换中,只需将待交换的节点的行或列换成要交换的行或列,举个例子:
有三元组节点N1={2,2,‘i’} // {行号,列号,字符信息},指令为“2 1 2”,意思就是要交换第1行和第2行。这时,我们可以很简单的将N1节点的行号2换为1即可,若第一行和第二行还有其他节点,交换行号即可,这样就完成了一次行交换。
很明显。这种方法对稀疏矩阵处理效率很高,但如果矩阵中大多是’i’和‘T’字符,这种稠密矩阵用这种方法很不佳。
第二种方法是直接malloc分配一个(mn)的空间,数组虽放不下,但要是存放mn个字符,128MB内存还是绰绰有余的。然后将所有矩阵数据依次存放到空间中。在交换行列时只需将对应行列的字符交换即可,但问题是如果是稠密矩阵,这个办法姑且不是太浪费,如果是稀疏矩阵那问题可就大了,白白浪费时间做了好多无用的字符交换,所以这种方法不可取。
以上两种方法处理都有局限性,所以我又想出一种巧妙的方法,就是利用索引数组。第一步和第二种方法一样,先malloc分配一个(m*n)的空间,依次将矩阵元素一行一行的存储到堆空间中。然后我们需要构造两个索引数组index_row[]与index_col[],一个放行号,一个放列号,最开始先初始化为顺序的行号和列号(1,2,3…)。但遇到交换行i和行j时,只需将互换index_row[i]和index_row[j]值即可,列也同理,只需互换index_col[i]和index_col[j]值即可。如果要查询某个位置的内容时只需查询第index_row[i]行,第index_col[j]列即可。这样就达到了即使不交换字符,也能够查到交换后的字符内容,省去了大量时间。下面举个例子再说明一下上述方案:
有以下字符内容:
1 | 2 | 3 | |
---|---|---|---|
1 | T | c | t |
2 | I | a | i |
3 | q | q | T |
index_row | 1 | 2 | 3 |
---|
index_col | 1 | 2 | 3 |
---|
执行指令 2 1 2:
修改index_row
index_row | 2 | 1 | 3 |
---|
执行指令 2 1 3:
修改index_row
index_row | 3 | 1 | 2 |
---|
查询[3,3]字符只需查询第index_row[3]行,第index_col[3]列,即[2,3],其他指令可以按上面自行模拟。
以上分析由于查询某个位置元素的内容相对简单,所以没有具体阐明。
第一种和第二种我都试了一下都超时了,而且第二种实现相对简单,所以就给出第一种方法源码和第三种,第三种内存4016 KB,运行时间101 MS,语言C,代码长度1764 B。
第一种源码:
#include <stdio.h>
#include <stdlib.h>
#define Empty 0
#define iPhone 1
#define Tree 2
typedef struct NODE{
int row;
int col;
char flag;
struct NODE *next;
}Node;
int main(){
int col,row,i,j,num;
int count = 0;
char *buf;
Node *head = NULL;
Node *node = NULL;
Node *p;
while(scanf("%d %d",&row,&col) != EOF){
count++;
buf = (char*)malloc(sizeof(char) * col);
for(i = 0; i < row; ++i){
scanf("%s",buf);
for(j = 0; j < col; ++j){
if(buf[j] == 'i' || buf[j] == 'T'){
node = (Node*)malloc(sizeof(Node));
node->row = i + 1;
node->col = j + 1;
node->flag = (buf[j] == 'i') ? iPhone : Tree;
node->next = head;
head = node;
}
}
}
scanf("%d",&num);
if(num >= 1)
printf("Case #%d:\n",count);
for(i = 0; i < num; ++i){
int arg1,arg2,arg3;
scanf("%d %d %d",&arg1,&arg2,&arg3);
if(arg1 == 1){
p = head;
while(p != NULL){
if(p->row == arg2 && p->col == arg3){
printf("%s\n",(p->flag == iPhone)? "Phone" : "Tree" );
break;
}
p = p->next;
}
if(p == NULL)
printf("Empty\n");
}
else if(arg1 == 2){
p = head;
while(p != NULL){
if(p->row == arg2){
p->row = arg3;
}
else if(p->row == arg3){
p->row = arg2;
}
p = p->next;
}
}
else if(arg1 == 3){
p = head;
while(p != NULL){
if(p->col == arg2){
p->col = arg3;
}
else if(p->col == arg3){
p->col = arg2;
}
p = p->next;
}
}
}
free(buf);
while(head != NULL){
p = head;
head = head->next;
free(p);
}
}
return 0;
}
第三种源码 :
#include <stdio.h>
#include <stdlib.h>
#define iPhone 'i'
#define Tree 'T'
int main(){
int col,row,i,j,num;
int count = 0;
char *buf;
int *index_col;
int *index_row;
while(scanf("%d %d",&row,&col) != EOF){
count++;
buf = (char*)malloc(sizeof(char) * col * row);
index_row = (int*)malloc(sizeof(int) * row);
index_col = (int*)malloc(sizeof(int) * col);
for(i = 0; i < col; ++i)
index_col[i] = i;
for(i = 0; i < row; ++i){
scanf("%s",buf+col*i);
index_row[i] = i;
}
scanf("%d",&num);
if(num >= 1)
printf("Case #%d:\n",count);
for(i = 0; i < num; ++i){
int arg1,arg2,arg3;
scanf("%d %d %d",&arg1,&arg2,&arg3);
if(arg1 == 1){
int offset = index_row[arg2-1] * col + index_col[arg3-1];
if(*(buf+offset) == iPhone || *(buf+offset) == Tree)
printf("%s\n",( *(buf+offset) == iPhone)? "Phone" : "Tree" );
else
printf("Empty\n");
}
else if(arg1 == 2){
int temp;
temp = index_row[arg2-1];
index_row[arg2-1] = index_row[arg3-1];
index_row[arg3-1] = temp;
}
else if(arg1 == 3){
int temp;
temp = index_col[arg2-1];
index_col[arg2-1] = index_col[arg3-1];
index_col[arg3-1] = temp;
}
}
free(buf);
}
return 0;
}