蓝桥杯算法提高 -- 周期字串

来源:互联网 发布:mac将图标放在桌面 编辑:程序博客网 时间:2024/05/18 01:56

思路:

相信大家都很容易想到, 根据字符串的长度, 求出所有约数, 然后按照约数的顺序来检验 . 但是检验的策略非常重要, 最重要的两点就是: 

(1)对每个不同长度周期的字符串, 最多只判断一次. 

(2)如果长度为N的字符串在原串的周期检验中不成立, 则长度为N的约数的字符串也不会成立 .


根据上述的结论, 我们可以大概感觉到, 我们不仅要求约数, 还要求约数的约数( 依次递归 ) , 而约数的约数本身又是原数的约数 , 至此, 可以断定使用多叉数结构(有向图)来进行约数存储( 我把它称为约数树 ). 


完全约数树: 如果根结点的所有约数都是根结点的下属结点(无论直接或间接), 则该树为完全约数树.


在贴代码之前, 我要对约数树的理论结构做出声明: 

(1) 描述整个问题域的约数树是一棵完全约数树, 但其所有子树都不一定是完全约数树( 这样可以避免约数重复而导致的周期判断重复 ).

(2) 从树根开始, 先序遍历约数树进行周期判断, 如果某结点判断失败, 根据策略2, 则其所有子树结点都将失败, 不必再检验.


以24为例, 给出如下约数树:


#include <iostream>#include <cmath>#include <memory.h>#include <cstring>using namespace std;#define MAXN 110/*约数树 : 子结点必然是根结点的约数, 但反之不成立*/struct Edge{// ...连接边 intnode;// ...指向结点的下标 Edge*next;// ...下一条边 }E[MAXN];// ...所有边的集合 struct CNode{// ...约数树结点 Edge*first;// ...首边 intminPossible;// ...该树范围内最小的可能周期( 留作剪枝用 ) boolisBuilt;// ...表示该结点是否建树完毕 }V[MAXN];// ...所有点的集合 int nEdgeCount = 0;// ...当前已经使用的边的总量 int strLen;// ...原字符串总长度 int minLen = MAXN;// ...当前已知的最小正周期 char ss[MAXN];// ...原字符串 // 添加一条从V[root]指向V[subRoot] 的边 // 复杂度:O(1) inline void AddEdge( int root, int subRoot ){Edge*pE = E+nEdgeCount++;pE->next = V[root].first;pE->node = subRoot;V[root].first = pE;}// 建立约数树  // 复杂度: O(?) void BuildTree( int root ){int Max = sqrt(root)+1;V[root].isBuilt = true;V[root].minPossible = root;V[root].first = NULL;for( int i = 2, j; i < Max; ++i ){if( root % i == 0 ){j = root/i;if( !V[j].isBuilt ) {AddEdge(root, j);BuildTree(j);V[root].minPossible = min(V[j].minPossible, V[root].minPossible);}if( !V[i].isBuilt ) {AddEdge(root, i);BuildTree(i);V[root].minPossible = min(V[i].minPossible, V[root].minPossible);}}}}// 匹配原串ss[0 ... length-1] 中是否存在长度为partLen的周期串// 复杂度:O(length) bool Match( int length, int partLen ){static char* tmp[MAXN];int station = length / partLen; // 段 tmp[0] = ss;for( int i = 1; i < station; ++i ) tmp[i] = tmp[i-1]+partLen;for( int i = 0; i < partLen; ++i, ++tmp[0]){for( int j = 1; j < station; ++j ){if( *tmp[j] != *tmp[0] ) return false;++tmp[j];}}return true;}// 匹配 SS[ 0 ... root-1] 中的最小周期串// 复杂度: O( root的约数总数 ) void Dfs_Match( int root ){minLen = min(root,minLen);for( Edge *e=V[root].first; e != NULL; e = e->next )if( V[e->node].minPossible < minLen )//...最优性剪枝 if( Match(root, e->node) )Dfs_Match( e->node );}int main(int argc, char** argv) {cin >> ss;strLen = strlen(ss);BuildTree(strLen);Dfs_Match( strLen );// Dfs中并没有检验是否有周期为1的可能性// 但如果该串的最小周期为1, 则其当前周期必然为2 if( minLen == 2 ) minLen = ss[0]==ss[1]?1:2;cout << minLen;return 0;}



最后说两句: 

以约数树为基的检验策略似乎没有从根源上减低时间复杂度, 甚至因为建树而耗损了一些额外的时间, 但其的确大大减低了原复杂度的系数, 整个算法从根本上来说, 只是一种可行性剪枝, 但本人最后没有办法算出建树的复杂度,所以无法准确推敲. 

0 0
原创粉丝点击