240 likes | 373 Views
程序设计实习. 习题讲解 李超 lichaopku@gmail.com. 最长上升子序列 (ai2757). 问题描述: 给出一个由 n 个数组成的序列 a[1..n] ,找出它的最长单调上升子序列。即求最大的 m, 使得存在 x 1 <x 2 <……<x m 且 a[x 1 ]<a[x 2 ]<……<a[x m ] 样例输入: 7 1 7 3 5 9 4 8 样例输出: 4. 动态规划解法. L[i] 表示从 1 到 i 这一段中以 i 结尾的最长上升子序列的长度 初始时设 L[i]=0,i=1,2…length(a)
E N D
程序设计实习 习题讲解 李超 lichaopku@gmail.com
最长上升子序列(ai2757) 问题描述: 给出一个由n个数组成的序列a[1..n],找出它的最长单调上升子序列。即求最大的m,使得存在x1<x2<……<xm且a[x1]<a[x2]<……<a[xm] 样例输入: 7 1 7 3 5 9 4 8 样例输出: 4
动态规划解法 L[i]表示从1到i这一段中以i结尾的最长上升子序列的长度 初始时设L[i]=0,i=1,2…length(a) 递推方程:L[i] = max{1, L[j]+1}, j=1,2,…i-1, 且a[j]<a[i]
动态规划解法 例: 1 7 3 5 9 4 8 1 2 1 1 2 2 1 2 2 3 1 2 3 4 2 2 2 4 3 1 3 1 2 2 3 4 3 4
动态规划解法 复杂性分析: 时间复杂度O(n2) O(n)=O(n-1)+n-2 L[i] = max{1, L[j]+1},j=1,2,…i-1, 且a[j]<a[i] 空间复杂度O(n)
二分法 思路: 开辟一个栈,每次取栈顶元素s和读到的元素a做比较 如果a>s,则加入栈; 如果a<s,则二分查找栈中的比a大的第1个数p,并用a替换p。 最后序列长度为栈的长度。 时间复杂度O(nlogn)
二分法 举例: Stack 1 3 7 5 4 8 9
二分法 该方法只能用于求最长递增子序列的长度,不能求子序列的具体值。 举例:1 5 9 4结果:1 4 9 长度3
木材加工(ai2774) Time Limit: 1000ms Memory limit: 65536kB 题目描述 木材厂有一些原木,现在想把这些木头切割成一些长度相同的小段木头,计算能够得到的小段木头的最大长度。如果连1厘米长的小段都切不出来,输出"0"。 样例输入 3 7 (1≦N ≦10000, 1≦K ≦10000) 232 (1≦ L1,……Ln ≦10000) 124 456 样例输出 114
动态规划解法 L[i]表示第 i 个木头的长度,用p[i][j]表示把前i个木头截为j段所能到的最大长度 1)当 L[i]<=p[i-1][j]时,p[i][j]=p[i-1][j] 2) 当 L[i]> p[i-1][j]时, p[i][j]=max{min(p[i-1][j-1],L[i]),min(p[i-1][j-2],L[i]/2),……min(p[i-1][0],L[i]/j)}另外,p[0][0]=0 ,p[0][1]=L[0], p[0][2]=L[0]/2,……P[0][k]=L[0]/k; 时间复杂度O(k2n) ,TLE
二分法 bool isok(int len);//判断切割K段每段长len是否可行left=0; right=最长木棍的长度;while(left<right-1){ mid=(right+left)/2; if(isok(mid)) left=mid; else right=mid;} 时间复杂度O(nlogM) , M表示最长木棍的长度
最短前缀(ai2797) 题目描述: 一个字符串的前缀是从该字符串的第一个字符起始的一个子串。例如“carbon”的前缀是: “c”,“ca”,“car”,“carb”,“carbo”, 和“carbon”。现在给你一组单词, 要求你找到唯一标识每个单词的最短前缀。样例: carbohydrate carboh cart cart carburetor carbu caramel cara caribou cari carbonic carboni cartilage carti carbon carbon carriage carr carton carto car car carbonate carbona
最短前缀(ai2797) 方法1 排序+和前后相邻字符串比较, O(nlogn) 方法2不排序,每个字符串和其他所有字符串比较,O(n2) 方法3 字典树, O(n)
最短前缀(ai2797) 方法1 排序+和前后相邻字符串比较, O(nlogn) 方法2不排序,每个字符串和其他所有字符串比较,O(n2) 方法3 字典树, O(n)
字典树(Trie Tree) 用于快速字符串检索的多叉树结构,空间换时间 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。 举例:abcd, abc, abd, b, bcd, efg, hi
用字典树求最短前缀 • char* getPrefix(char * word){ • temp=“”; prefix=“”; isForked=false; • while(1){ • 从根向下行进,temp = temp+c; //c为中间结点的字符值 • ff(isForked==true){ • isForked=false; • prefix=prefix+temp; • temp=“”; • } • if (分叉 or 结点为红色) • isForked==true; • if(路径字符串==word){ • if(是叶结点) • return prefix; • else • return word; • } • } • }
用字典树求最短前缀 方法2(队列实现) 用”#”表示分叉或者红色结点 if(中间结点) 返回队列全部元素(‘#’除外) if(叶结点) 返回最后一个’#’前的元素+’#’后第一个元素 例: abc bcd
用字典树求最短前缀 时间复杂度: O(n) 建树 O(n) 求最短前缀 O(n) 空间复杂度: O(n)
棋盘问题(poj1321) Description 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。 Input 输入含有多组测试数据。 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n 当为-1 -1时表示输入结束。 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
棋盘问题(poj1321) Output 对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。 Sample Input 2 1 #. .# 4 4 ...# ..#. .#.. #... -1 -1 Sample Output 2 1
棋盘问题(poj1321) #include<iostream> #include<cstring> using namespace std; const int MAX = 8; char board[MAX][MAX];//记录棋盘状态 bool placed_c[MAX];//记录一列是否已经放过棋子 int count;//放棋子的方案数 int num_p;//已放棋子数目 int n,k;//棋盘n*n,放的棋子数k
棋盘问题(poj1321) /* 是否可以放棋子 */ bool can_place(int i,int j){ return !placed_c[j] && board[i][j] == '#'; } /* 深搜/回溯 */ void DFS(int i){ if(num_p == k){ count++; return; } if(n-i<k-num_p) //剪枝,如果剩余行数<剩余棋子数,退出 return; if(i >= n) return; for(int j = 0; j < n; j++){ if(can_place(i,j)){ placed_c[j] = true; num_p++; DFS(i+1); placed_c[j] = false; //j列不放棋子 num_p--; } } //endoffor DFS(i+1);//i行不放棋子,回溯 } //endofDFS
棋盘问题(poj1321) int main(){ int i,j; while(cin >> n >> k, k != -1){ for(i = 0; i < n; i++) for(j = 0; j < n; j++) cin >> board[i][j]; count = 0; num_p = 0; memset(placed_c,false,sizeof(placed_c)); DFS(0); cout << count << endl; } return 0; }
Thanks! Any question?