370 likes | 595 Views
习题选讲. 搜索. 搜索. 1152 1153 马周游 Queens, Knights and Pawns 1024 Magic Island 1050 Numbers & Letters 1006 Team Rankings 1150 1151 1515 魔板. 题目大意 : 一个有限大小的棋盘上有一只马,马只能按日字方式走,如图所示。 给出初始时马的位置,找出一条马移动的路线,经过所有格子各一次。. 1152 1153 马周游. 1152 1153 马周游. 解题思路: 枚举马能走的所有路径,直至找到一条完成周游的路径; 递归,回溯。.
E N D
习题选讲 搜索
搜索 • 1152 1153 马周游 • Queens, Knights and Pawns • 1024 Magic Island • 1050 Numbers & Letters • 1006 Team Rankings • 1150 1151 1515 魔板
题目大意: 一个有限大小的棋盘上有一只马,马只能按日字方式走,如图所示。 给出初始时马的位置,找出一条马移动的路线,经过所有格子各一次。 1152 1153 马周游
1152 1153 马周游 • 解题思路: • 枚举马能走的所有路径,直至找到一条完成周游的路径; • 递归,回溯。
1152 1153 马周游 • bool solve(int x, int y, int lev) { • route[lev] = x * N + y; • if (lev == M * N - 1) {print_route();return true; } • visited[x][y] = true; • grid grids[8]; • int n=get_grid(grids,x,y); • for (i=0; i<n; i++) • if (solve(grids[i].x, grids[i].y, lev+1)) • return true; • visited[x][y] = false; • return false; • }
1152 1153 马周游 • int get_grid(grid grids[], int x,int y) { • int n=0; • for (int i=0; i<8; i++) { • int xx = x + direction[i][0]; • int yy = y + direction[i][1]; • if (xx>=0&&yy>=0&&xx<M&&yy<N&&!visited[xx][yy]) { • grids[n].x = xx; • grids[n].y = yy; • n++; • } • } • return n; • }
1152 1153 马周游 • 以上程序速度过慢。 • 优化:改变搜索顺序。 • 先搜索可行格较少的格子。 • 其他顺序。 • 修改get_grid()函数。
1152 1153 马周游 • int get_grid(grid grids[], int x,int y) { • int n=0; • for (int i=0; i<8; i++) { • int xx = x + direction[i][0] • int yy = y + direction[i][1]; • if (xx>=0&&yy>=0&&xx<M&&yy<N&&!visited[xx][yy]) { • grids[n].x = xx; grids[n].y = yy; • grids[n].count = get_count(xx, yy); • n++; • } • } • sort(grids,grids+n); • return n; • }
1152 1153 马周游 • bool operator < (const grid &a, const grid &b) { • return a.count < b.count; • } • int get_count(int x, int y) { • int i, xx, yy, count = 0; • for (i=0; i<8; i++) { • xx = x + direction[i][0]; • yy = y + direction[i][1]; • if (xx>=0&&yy>=0&&xx<M&&yy<N&&!visited[xx][yy]) • count++; • } • return count; • }
不推荐! 1152 简单的马周游问题 const • 隐藏算法: • 5*6规模比较小 • 仅30种输入 • 每种的输出仅30个整数 • 完全可以使用const大法 • 先在本机跑出所有结果,然后O(1)输出
1172 Queens, Knights and Pawns • 题目大意: • 给出棋盘大小,给出每个后、马和兵的位置,求棋盘上有多个个没被占领的格子不会受到后也不会受到马的攻击。 • 棋盘大小最多为1000*1000,每种棋子最多100个。
1172 Queens, Knights and Pawns • 解题思路: • 用二维数组表示一个棋盘,标记每个棋子的位置,再标记每个棋子能攻击的位置,最后计算有多少个位置不会被攻击。
1172 Queens, Knights and Pawns • enum grid_state {empty,occupied,attacked}; • grid_state grid[1001][1001]; • int cal(vector<point> q, vector<point> k, vector<point> p) • { • memset(grid,0,sizeof(grid)); • occupy(q); occupy(k); occupy(p); • attackQ(q); attackK(k); • int s=0; • for (int i=1;i<=row;i++) • for (int j<=col;j++) • if (grid[i][j]==empty) • s++; • return s; • }
1172 Queens, Knights and Pawns • bool in_board_and_unoccupied(point p) { • if (p.x>=1&&p.x<=row) • if (p.y>=1&&p.y<=col) • return grid[p.x][p.y]!=occupied; • return false; • } • void occupy(vector<point> v) { • for (int i=0;i<v.size();i++) • grid[v[i].x][v[i].y]]=occupied; • }
1172 Queens, Knights and Pawns • int dQ[8][2]={{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1},{1,1}}; • void attackQ(vector<point> q) { • for (int i=0;i<q.size();i++) • for (int dir=0;dir<8;dir++) { • point temp(q[i].x+dQ[dir][0],q[i].y+dQ[dir][1]); • while (in_board_and_unoccupied(temp) { • grid[temp.x][temp.y]=attacked; • temp=point(temp.x+dQ[dir][0],temp.y+dQ[dir][1]; • } • } • }
1172 Queens, Knights and Pawns • int dK[8][2]={{1,2},{1,-2},{2,-1},{-2,-1},{-1,-2},{-1,2},{-2,1},{2,1}}; • void attackK(vector<point> k) { • for (int i=0;i<k.size();i++) • for (int dir=0;dir<8;dir++) { • point temp(k[i].x+dK[dir][0],k[i].y+dK[dir][1]); • if (in_board_and_unoccupied(temp) • grid[temp.x][temp.y]=attacked; • } • }
1024 Magic Island • 题目大意: • 给出一个有N个节点的树,从结点K开始出发,不重复经过任意一条边,求最远可以走的路程。
1024 Magic Island • 解题思路: • 树的性质:两个节点不经过重复边的路径有且只有一条。 • 从起点开始BFS或DFS,求出到所有节点的不经过重复边的路径长度,取其中的最大值即为答案。 struct node { int n; //顶点编号 int l; //起始点到该点的最大长度 }; vector<node> edge[N]; void dfs(intcur,intp,intl) { if (l>ans) ans=l; for (inti=0;i<edge[cur].length();i++) if (edge[cur][i].n!=p) dfs(edge[cur][i].n,cur,l+edge[cur][i].l); }
1050 Numbers & Letters • 题目大意 • 给5个整数,使用+, -, *, / 四种运算,可任意安排顺序和加括号,求一个不超过某给定值的最优解 • 解题思路 • Dfs(S) //S为操作数的集合 • 从S中任取两个数a, b进行运算得c • dfs(S-{a,b}+{c}) • 复杂度:C(5,2)*4*C(4,2)*4*C(3,2)*4*C(2,2)*4 = 46080
1050 Numbers & Letters void dfs(int a[], int n) { if (n==1) return; intb[5],m=0; for(inti=0;i<n;i++) for(intj=i+i;j<n;j++) { for(intk=0;k<n;k++) if (k!=i && k!=j) b[m++]=a[k]; update_answer(b[m]=a[i]+a[j]); dfs(b,m+1); update_answer(b[m]=a[i]-a[j]); dfs(b,m+1); update_answer(b[m]=a[j]-a[i]); dfs(b,m+1); update_answer(b[m]=a[i]*a[j]); dfs(b,m+1); if (a[j]!=0 && a[i]%a[j]==0) {update_answer(b[m]=a[i]/a[j]); dfs(b,m+1); } if (a[i]!=0 && a[j]%a[i]==0) {update_answer(b[m]=a[j]/a[i]); dfs(b,m+1); } } }
1006 Team Rankings • 题目大意: • 对于两个排列p, q,定义 distance( p, q )为在p, q中出现的相对次序不同的元素的对数。相当于以p为基准,求q的逆序数。 • 给出n个5元排列,构造一个排列,使得该排列对n个排列的distance之和最小。 • n<=100
1006 Team Rankings • 解题思路: • 枚举所有5元排列,与n个排列一一比较5个元素之间顺序并累加; • 枚举方法可用递归。 • 求逆序数的算法 • 平方级枚举 n^2 • 规模较大时可采用归并排序 nlogn
1006 Team Rankings void dfs(char rank[],intlev) { if (lev==5) { rank[5]='\0'; cal(rank); return; } for (char c='A';c<='E';c++) if (!used[c]) { rank[lev]=c; used[c]=true; dfs(rank,lev+1); used[c]=false; } }
1006 Team Rankings void cal(char rank[]) { int count=0; for (inti=0;i<n;i++) count+=distance(ranks[i],rank); if (count<ans_count) { ans_count=count; strcpy(ans,rank); } }
1006 Team Rankings int distance(char a[],char b[]) { intcnt=0; for (inti=0;i<5;i++) { posa[a[i]]=i; posb[b[i]]=i; } for (char c1='A';c1<='E';c1++) for (char c2=c1+1;c2<='E';c2++) if ((posa[c1]-posa[c2])*(posb[c1]-posb[c2])<0) cnt++; return cnt; }
Tips What is求逆序数的快速算法? 归并排序的原理 What is nlogn? 通常出现的算法复杂度级别 O(mn),O(n!),O(nm),O(n),O(logn),O(1) 当n>10000时,至少要O(nlogn)
115011511515魔板 • 题目大意 • 魔板是2行4列的方格,八格分别标为1-8 • 初始状态为1 2 3 4 8 7 6 5 • 有三种操作: • 上下两行互换 1 2 3 4 8 7 6 5
115011511515魔板 • 题目大意 • 魔板是2行4列的方格,八格分别标为1-8 • 初始状态为1 2 3 4 8 7 6 5 • 有三种操作: • 上下两行互换 • 每行循环右移一格 1 2 3 4 8 7 6 5
115011511515魔板 • 题目大意 • 魔板是2行4列的方格,八格分别标为1-8 • 初始状态为1 2 3 4 8 7 6 5 • 有三种操作: • 上下两行互换 • 每行循环右移一格 • 中间四块顺时针转一格 1 2 3 4 8 7 6 5
115011511515魔板 • 题目大意 • 魔板是2行4列的方格,八格分别标为1-8 • 初始状态为1 2 3 4 8 7 6 5 • 有三种操作: • 上下两行互换 • 每行循环右移一格 • 中间四块顺时针转一格 • 给定一个终止状态,求最小操作数及方案 1 4 3 2 1 3 8 4 8 5 2 7 6 7 5 6
115011511515魔板 • 解题思路 • 对模板进行状态搜索 • 由一种状态可以转移到另外三种状态,搜索树为一棵三叉树 • 在这棵三叉树上搜索,目的是求出最优解
115011511515魔板 效果: 加优化后勉强可过1150 很傻很天真 评价: • 算法一:盲目DFS • 对这棵三叉树进行DFS • 若想求得最优解,需要遍历整棵树 • 需要进行重复扩展 • 优化:若已找到一个可行解,可剪去大于等于这个深度的所有子树
115011511515魔板 效果: 轻松切掉1150,但过不了1151 很慢很暴力 评价: • 算法二:BFS • 对这棵三叉树进行BFS • 第一个可行解即是最优解
115011511515魔板 效果: 轻松切掉以上三题 很快很易写 评价: • 算法三:DFS/BFS + 判重 • 可将一个魔板的状态编码,用8!以内的整数表示一个状态 • 可用8!的数组判重。每经过一个节点,把它放进已搜索列表中,每遇到一个节点,如果在已搜索列表存在,则不再扩展该节点。 • 不管DFS还是BFS,最多搜8!=40320个状态
1150 1151 1515 魔板 • state q[40320]; • void update(state new_state,int &head,int &tail, char opt) { • q[head]=new_state; • visit[hash(new_state)]=true; • parent[head]=tail; • op[head++]=opt; • }
1150 1151 1515 魔板 • void bfs(state start) { • int head=0,tail=0; • update(start,head,tail,'\0'); • while (tail<head) { • state new_state=A(q[tail]); • if (visit[hash(new_state)]==false) • update(new_state,head,tail,'A') • new_state=B(q[tail]); • if (visit[hash(new_state)]==false) • update(new_state,head,tail,'B') • state new_state=C(q[tail]); • if (visit[hash(new_state)]==false) • update(new_state,head,tail,'C') • tail++; • } • }