您的位置:首页 > 其它

算法入门经典2 第5章解题报告

2016-12-26 16:24 295 查看
【题目链接】点击打开链接

【写在前面】 这章的部分习题很难,我现在的能力并不能全部做出来这些题,暂时我只能做到这里了,接下来我把我做出来的题目分享一下解题方法,等自己能够做剩下的题了,再回来解决这些题。然后由于书上的例题和习题的题意都有中文描述,我就不写题意了,只写我的做法。

【例题篇】

【5-2】由于每个木块堆的高度不确定,所以用vector来存是非常合适的,并且木块堆的个数不超过n,所以用一个vector数组来存,然后就是对vector的一些操作了,很简单。代码在书上的110页。

【5-3】本题就是展示set的用法,由于string已经定义了“小于”运算符,直接用set保存单词集合就可以了。这里还有个技巧,就是把所有输入非字母的字符变成空格,用stringstream来读取会方便很多。

【5-4】把每个单词标准化,即是把所有字母全部变成小写再进行排序,然后再放到map中进行统计。

【5-5】堆栈的运用。本题的集合并不是简单的整数集合或者字符串集合,而是集合的集合。为了方便起见,此处为每个不同的集合分配一个唯一的ID,则每个集合都可以表示成所包含元素的ID集合,这样就可以用STL的set<int>来表示了,而整个栈则是一个stack<int>。

【5-6】本题有两个队列:每个团队有一个队列,而团队整体又形成了一个队列。例如,有3个团队1,2,3,队员集合分别为{301, 303, 103, 101, 102, 201},则3个团队的队列分别为{103, 101, 102}, {201}, {301, 303},这个很显然吧。然后整体的队列为{3, 1, 2}。接下来模拟题上的过程即可。

【5-8】模拟。首先计算出M,然后根据M退出行数,然后依次输出即可。给一下这个题的代码吧。

string in[110];

int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
int maxlen = 0;
for(int i = 0; i < n; i++){
cin>>in[i];
maxlen = max(maxlen, (int)in[i].size());
}
int col = 62 / (maxlen + 2);
int row = ceil(n*1.0/col);
sort(in, in + n);
for(int i = 0; i < 60 ;i++) printf("-"); printf("\n");
for(int i = 0; i < row; i++){
for(int j = 0; j < col; j++){
int id = i + j * row;
if(id >= n) continue;
cout<<in[id];
int k = maxlen - in[id].size();
while(k--) printf(" ");
if(j != col - 1) printf("  ");
}
if(i <= row - 1) printf("\n");
}
}
return 0;
}


【5-9】解题方法为只枚举c1, c2,然后从上到下扫描每一行。每次碰到一个新的行r,把c1, c2两列的内容作为一个二元组存到map 里面,如果map里面的键值已经存在这个二元组,该二元组映射到的就是所要求的的r1,当前行就是r2。然后这里还有一个问题,就是如何表示c1,c2两列组成的二元组?这里采用了map标号的方法。

map <string, int> ID1;
map <pair<int, int> , int> ID2;
int a[10010][11], cnt;

int main()
{
int n, m;
while(scanf("%d%d", &n,&m) != EOF)
{
cnt = 1;
getchar();
ID1.clear();
ID2.clear();
memset(a, 0, sizeof(a));
int cnt2;
//编码预处理
string str;
for(int i = 0; i < n; i++){
cnt2 = 0;
string x;
getline(cin, str);
int len = str.size();
for(int j = 0; j < len; j++){
if(str[j] != ',') x += str[j];
if(str[j] == ',' || j == len - 1){
if(ID1.count(x) == 0){
ID1[x] = cnt++;
}
a[i][cnt2++] = ID1[x];
x.clear();
}
}
}
//找答案
bool flag = 0;
for(int j = 0; j < m - 1; j++){
for(int k = j + 1; k < m; k++){
ID2.clear();
for(int i = 0; i < n; i++){
int x = a[i][j], y = a[i][k];
if(ID2.count(MP(x, y))){
printf("NO\n");
printf("%d %d\n", ID2[MP(x, y)], i + 1);
printf("%d %d\n", j + 1, k + 1);
flag = 1;
break;
}
else{
ID2[MP(x, y)] = i + 1;
}
}
if(flag) break;
}
if(flag) break;
}
if(flag == 0) printf("YES\n");
}
return 0;
}


【5-12】 注意到建筑物的可见性等于南墙的可见性,可以在输入之后直接忽略“深度”这个参数。接下来把建筑物按照输出顺序排序,然后依次判断每个建筑物是否可见。判断可见性看起来比较麻烦,因为一个建筑物可能只有部分可见,无法枚举所有x坐标,来查看这个建筑物在该处是否可见,因为x坐标有无穷多个。解决的方法有很多种,最常见的是离散化,即把无穷变为有限。具体方法是:把所有的x坐标排序去重,则任意两个x坐标形成的区间具有相同的可见性,一个区间要么完全可见,要么完全不可见,这样只需要在这个区间里面任意选一个点(比如中点),就能判断出一个建筑物是否在整个区间是否可见。如何判断一个建筑物是否在某个坐标处是否可见呢?首先,建筑物的坐标中必须包含这个x坐标,其次,建筑物南边不能有另外一个建筑物包含这个x坐标,并且不比他矮。
复杂度O(n*n*n)。代码见133页

【习题篇】

【5-1】 看懂样例,大概就推断出我们要找到每一列的单词的长度的最大值,然后输出这个长度+2的字符,当然直接这样做了会返回WA。这里有个坑点,最后一列不能输出空格,只看了书上的中文题意没看PDF,一直没发现,看来看题还是结合PDF来看,避免不必要的坑点。

string input, s;
vector <string> v[1100];
int maxlen[200];
int main()
{
for(int i = 0; i < 1100; i++) v[i].clear();
int row = 0, cnt;
while(getline(cin, input))
{
cnt = 0;
stringstream ss(input);
while(ss >> s){
v[row].push_back(s);
maxlen[cnt] = max(maxlen[cnt], (int)s.size());
cnt++;
}
row++;
}
for(int i = 0; i < row; i++){
for(int j = 0; j < (int)v[i].size(); j++){
cout<<v[i][j];
int t = maxlen[j] - v[i][j].size();
if(j != v[i].size() - 1){
printf(" ");
while(t--) printf(" ");
}
}
printf("\n");
}
return 0;
}


【5-2】 如题意说的,我们模拟1000轮就可以了,没什么好说的。

vector <int> v[1002];

int main()
{
int T, n;
scanf("%d", &T);
while(T--)
{
for(int i = 0; i < 1000; i++) v[i].clear();
scanf("%d", &n);
bool loop = false;
for(int i = 0; i < n; i++){
int x;
scanf("%d", &x);
v[0].push_back(x);
}
bool zero = 0;
for(int i = 1; i <= 1000; i++){
int pos = v[i-1][0];
for(int j = 0; j < n; j++){
if(j < n - 1){
int t = abs(v[i-1][j] - v[i-1][j+1]);
v[i].push_back(t);
}
else{
int t = abs(v[i-1][j] - pos);
v[i].push_back(t);
}
}
bool ok1 = 0;
for(int j = 0; j < n; j++){
if(v[i][j] != 0){
ok1 = 1;
}
}
if(ok1 == 0 && i <= 1000){
zero = 1;
break;
}
for(int k = 0; k < i; k++){
int j;
for(j = 0; j < n; j++){
if(v[k][j] != v[i][j]){
break;
}
}
if(j >= n){
loop = true;
break;
}
}
if(loop) break;
}
if(zero == 1){
printf("ZERO\n");
}
else if(loop){
printf("LOOP\n");
}
else{
printf("ZERO\n");
}
}
return 0;
}


【5-3】一看这题就是典型用双端队列的题目,就是输出要注意一个东西,只有一个元素的输出情况特殊,要看清楚。这里被坑了很多发。

deque <int> que;
int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
if(n == 0) break;
while(!que.empty()) que.pop_back();
for(int i = 1; i <= n; i++) que.push_back(i);
if(n == 1) printf("Discarded cards:\n");
else printf("Discarded cards: ");
while(!que.empty())
{
int x = que.front();
que.pop_front();
if(que.empty()){
printf("Remaining card: %d\n", x);
}
else{
int y = que.front();
que.pop_front();
que.push_back(y);
if(que.size() == 1)
printf("%d\n", x);
else{
printf("%d, ", x);
}
}
}
}
return 0;
}


【5-4】 map的简单应用,简单映射一下即可,直接上代码吧。

map <pair <int, int> ,int> mp;

int main()
{
int n;
while(scanf("%d", &n) != EOF)
{
if(n == 0) break;
mp.clear();
int x, y;
for(int i = 0; i < n; i++){
scanf("%d%d", &x, &y);
mp[MP(x, y)]++;
}
bool flag = 1;
for(map<pair<int,int>, int>::iterator it = mp.begin(); it != mp.end(); it++){
int x = (it->first).first;
int y = (it->first).second;
if(mp[MP(y, x)] != mp[MP(x, y)]){
flag = 0;
break;
}
}
if(flag){
printf("YES\n");
}
else{
printf("NO\n");
}
}
return 0;
}


【5-5】set+string,没有难度。

set <string> s1;
string s[120010];
int main()
{
s1.clear();
string t;
int cnt = 0;
while(cin>>t){
s[cnt++] = t;
s1.insert(t);
}
string a = "", b = "";
for(int i = 0; i < cnt; i++){
int len = s[i].length();
for(int j = 1; j < len; j++){
a = s[i].substr(0, j);
b = s[i].substr(j, len);
if(s1.find(a) != s1.end() && s1.find(b) != s1.end()){
cout<<s[i]<<endl;
break;
}
}
}
return 0;
}


【5-6】这个题就有意思了,仔细分析就会发现对称轴肯定是最小的横坐标和最大的横坐标的和的一半,然后判断就行了,对于每个点去找一下有没有对称点,注意对称点要保证y坐标相同。

struct noed{
int x, y;
}p[1100];
bool vis[1100];
int main()
{
int n, T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
int minn = 1e9, maxx = -1e9;
for(int i = 0; i < n; i++){
scanf("%d%d", &p[i].x,&p[i].y);
p[i].x *= 2;
minn = min(minn, p[i].x);
maxx = max(maxx, p[i].x);
}
int ansx = (minn + maxx) / 2;
memset(vis, false, sizeof(vis));
for(int i = 0; i < n; i++){
if(p[i].x == ansx){
vis[i] = true;
}
}
for(int i = 0; i < n; i++){
if(p[i].x <= ansx){
int d = ansx - p[i].x;
for(int j = 0; j < n; j++){
if(j == i) continue;
if(p[j].x >=ansx && (p[j].x == p[i].x + 2*d) && (p[j].y == p[i].y)){
vis[i] = 1;
vis[j] = 1;
}
}
}
}
bool ok = 1;
for(int i = 0; i < n; i++){
if(!vis[i]){
printf("NO\n");
ok = 0;
break;
}
}
if(ok){
printf("YES\n");
}
}
return 0;
}


【5-7】 又是个双端队列的题目,直接按照题意用双队列模拟这个过程就可以了。

int vis[10];
deque <pair<int, int> > que;

int main()
{
int T, pos, n;
scanf("%d", &T);
while(T--)
{
int ans = 1;
while(!que.empty()) que.pop_front();
memset(vis, 0, sizeof(vis));
scanf("%d%d", &n,&pos);
for(int i = 0; i < n; i++){
int x;
scanf("%d", &x);
que.push_back(MP(x, i));
vis[x]++;
}
bool flag = 0;
while(que.size())
{
int mx = 0;
for(int i = 9; i >= 0; i--) if(vis[i]){
mx = i;
break;
}
while(1)
{
pair<int, int> x = que.front();
que.pop_front();
if(x.first != mx){
que.push_back(x);
}
else{
if(x.second == pos){
flag = 1;
break;
}
vis[x.first]--;
ans++;
break;
}
}
if(flag) break;
}
printf("%d\n", ans);
}
return 0;
}


【5-8】string+map+vector的应用,过了这题会发现stl的强大之处。没有什么特殊的方法,就本分的模拟就可以了。

struct book{
string author;
int state;
};
map <string, book> book1;
vector <string> name;
bool cmp(string a, string b)
{
if(book1[a].author == book1[b].author){
return a < b;
}
else{
return book1[a].author < book1[b].author;
}
}

int main()
{
//预处理
string s;
while(getline(cin, s)){
if(s == "END") break;
string s1 = s.substr(0, s.find_last_of("\"") + 1);
book b1;
b1.author = s.substr(s.find_last_of("\"") + 1);
name.push_back(s1);
book1[s1] = b1;
}
sort(name.begin(), name.end(), cmp);
int len = name.size();
for(int i = 0; i < len; i++){
book1[name[i]].state = 1;
}
//处理查询
string x;
while(cin >> s)
{
if(s == "END") break;
if(s == "BORROW")
{
getchar();
getline(cin, x);
book1[x].state = 0;
}
if(s == "RETURN")
{
getchar();
getline(cin, x);
book1[x].state = -1;
}
if(s == "SHELVE")
{
int len = name.size();
for(int i = 0; i < len; i++){
if(book1[name[i]].state == -1){
int j;
for(j = i; j >= 0; j--){
if(book1[name[j]].state == 1) break;
}
if(j > -1){
cout<<"Put " << name[i]<<" after "<<name[j]<<endl;
}
else
{
cout<<"Put "<<name[i]<<" first"<<endl;
}
book1[name[i]].state  =1;
}
}
cout<<"END"<<endl;
}
}
return 0;
}


【5-9】这道题难度比较大,我参考了网上的一个人的思路和代码A掉的,主要是那个嵌套的地方开始不知道怎么下手,看了题解之后,发现这不就是个递归的结构吗?用DFS不是可以完美解决吗?然后这题需要大量的字符串处理操作和技巧,花了很久很久调试,我第一次调试的时候,少输入了一个字符串却一直没发现这个问题。。

int bug;
vector <string> code;
map <char, int> a;
map <string, int> val;

int solve1(string str)
{

if(str.find("[") == string::npos) return stoi(str);
if(count(str.begin(), str.end(), '[') == 1){
if(!val.count(str)) return -1;
return val[str];
}
if(count(str.begin(), str.end(), '[') >= 2){
string now = str.substr(str.find('[') + 1);
int v = solve1(now);
if(!val.count(str.substr(0, 2) + to_string(v))) return -1;
return val[str.substr(0, 2) + to_string(v)];
}
return -1;
}

void solve2(const string str)
{
stringstream ss(str.substr(2));
int v;
ss >> v;
//cout<<v<<endl;
a[str[0]] = v;
}

int main()
{
while(1)
{
bug = -1;
code.clear();
a.clear();
val.clear();
string line;
cin>>line;
if(line == ".") break;
while(1){
code.push_back(line);
cin>>line;
if(line == ".") break;
}
for(size_t i = 0; i < code.size(); i++){
if(code[i].find("=") == string::npos){
solve2(code[i]);
}
else{
string l = code[i].substr(0, code[i].find('='));
string r = code[i].substr(code[i].find('=') + 1);
int rval = solve1(r.substr(0, r.find(']')));
int lval = solve1(l.substr(l.find('[') + 1, l.find(']') - l.find('[') - 1));
if(lval == -1 || rval == -1){
bug = i;
break;
}
if(!a.count(code[i][0]) || lval >= a[code[i][0]]){
bug = i;
break;
}
string name = code[i].substr(0, 2) + to_string(lval);
val[name] = rval;
}
}
printf("%d\n", bug+1);
}
return 0;
}


【5-11】纯粹考察模拟能力和STL的认识能力了。注意分离key和value然后用string来处理,map来映射之后让这道题的难度瞬间下降。

map <string, string> m1, m2;
map <string, string>::iterator it1, it2;
string s1[110], s2[110], s3[110];

int main()
{
int T, cnt1, cnt2, cnt3;
scanf("%d", &T);
while(T--)
{
//init
cnt1 = cnt2 = cnt3 = 0;
m1.clear();
m2.clear();
//input
char c;
string input1 = "";
cin>>c;
while(cin >> c && c != '}'){
if(c != ':' && c != ',') input1 += c;
else if(c == ':'){
string input2 = "";
while(cin >> c && c != ',' && c != '}') input2 += c;
m2[input1] = input2;
if(c == '}') break;
if(c == ',') input1 = "";
}
}
m1 = m2;
m2.clear();
input1 = "";
cin>>c;
while(cin >> c && c != '}'){
if(c != ':' && c != ',') input1 += c;
else if(c == ':'){
string input2 = "";
while(cin >> c && c != ',' && c != '}') input2 += c;
m2[input1] = input2;
if(c == '}') break;
if(c == ',') input1 = "";
}
}
//deal with answer
for(it1 = m1.begin(); it1 != m1.end(); it1++){ //减少的
it2 = m2.find(it1 -> first);
if(it2 == m2.end()) s2[cnt2++] = it1 -> first;
}
for(it1 = m2.begin(); it1 != m2.end(); it1++){
it2 = m1.find(it1 -> first);
if(it2 != m1.end() && it2 -> second != it1 -> second) s3[cnt3++] = it1 -> first; //找到并不同
if(it2 == m1.end()) s1[cnt1++] = it1 -> first; //增加的
}
if(cnt1 == 0 && cnt2 == 0 && cnt3 == 0)
{
printf("No changes\n");
}
else{
if(cnt1 != 0)
{
cout<<"+"<<s1[0];
for(int i = 1; i < cnt1; i++){
cout<<','<<s1[i];
}
cout<<endl;
}
if(cnt2 != 0)
{
cout<<"-"<<s2[0];
for(int i = 1; i < cnt2; i++){
cout<<','<<s2[i];
}
cout<<endl;
}
if(cnt3 != 0)
{
cout<<"*"<<s3[0];
for(int i = 1; i < cnt3; i++){
cout<<','<<s3[i];
}
cout<<endl;
}
}
printf("\n");
}
return 0;
}


【5-15】本题询问的数是大数,用字符形式表示,且对应的斐波纳契数列下标可达100000,也很容易加到很大,因此都必须用字符串进行大数加法。同时题目询问的大数不超过40,就是超过40位的时候就应该进行截断处理:只保留高40位,但在实际运算的时候低位可能产生进位,且可能进位是有很远的低位引起的,为了保证正确,必须在运算时保留多余40很多的。

40位一位一位比要比40次,且每次有10种(1到9),不能自己开个hash表否则超内存,又不敢用map可能超时(STL库函数虽方便但效率不高),可以自己建个字典树,最多查找40次,很大优化。

char c[100];
char str[3][100];
void add(char a[], char b[], char bas[]) //高精度加法
{
int x, y, z, up, i, j, k;
k = up = 0;
i = strlen(a) - 1;
j = strlen(b) - 1;
while(i >= 0 || j >= 0)
{
if(i < 0) x  = 0;
else x = a[i] - '0';
if(j < 0) y = 0;
else y = b[j] - '0';
z = x + y + up;
c[k++] = z % 10 + '0';
up = z / 10;
i--, j--;
}
if(up > 0) c[k++] = up + '0';
for(int i = 0; i < k; i++) bas[i] = c[k - i - 1];
bas[k] = '\0';
}

typedef struct node{
int id; //标记是否为单词节点
node *nex[10];
}Trie;

Trie *root;

void Insert(char str[], int idx)
{
node *t, *s = root;
int len = strlen(str);
for(int i = 0; i < len && i < 41; i++)
{
int id = str[i] - '0';
if(s->nex[id] == NULL)
{
t = new node;
for(int j = 0; j < maxs; j++) t -> nex[j] = NULL;
t -> id = -1;
s -> nex[id] = t;
}
s = s -> nex[id];
if(s -> id < 0)
{
s -> id = idx;
}
}
}

int Find(char str[])
{
int len = strlen(str);
node *s = root;
int cnt;
for(int i = 0; i < len; i++)
{
int id = str[i] - '0';
if(s -> nex[id] == NULL)
{
return -1;
}
else
{
s = s -> nex[id];
cnt = s -> id;
}
}
return cnt;
}

void Delete(node *p)
{
for(int i = 0; i < 10; i++){
if(p -> nex[i] != NULL){
Delete(p -> nex[i]);
}
}
free(p);
}

int main()
{
root = new node;
for(int i = 0; i < 10; i++) root -> nex[i] = NULL;
root -> id = -1;
str[0][0] = '1';
str[0][1] = 0;
Insert(str[0], 0);
str[1][0] = '1';
str[1][1] = 0;
Insert(str[1], 1);
for(int i = 2; i < 100000; i++)
{
int len1 = strlen(str[0]);
int len2 = strlen(str[1]);
if(len2 > 60)
{
str[1][len2 - 1] = 0;
str[0][len1 - 1] = 0;
}
add(str[0], str[1], str[2]);
Insert(str[2], i);
strcpy(str[0], str[1]);
strcpy(str[1], str[2]);
}
char s[60];
int T, ks = 0;
scanf("%d", &T);
while(T--)
{
scanf("%s", s);
printf("Case #%d: %d\n", ++ks, Find(s));
}
Delete(root);
return 0;
}


【后记】通过这几天对这一章的训练,我越发体会到了STL的强大操作,但是STL也有一定的局限性,诸如set等都有较大常数,所以要慎用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: