分类: OI笔记

  • Treap树 模板

    Treap是一种非常好写而且易于理解的平衡树,然而效率基本看脸,毕竟用到了随机算法。

    期望情况下 插入 删除等操作的复杂度均为$$O(log n)$$

    详情见注释

     

    #include 
    
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    using int_t = long long int;
    //表示treap中的一个节点
    struct Node {
        //该节点的值
        int_t value;
        //该节点的随机优先级,用于将treap组织成一个小根堆
        //但堆是完全平衡的 而二叉搜索树显然不容易完全平衡 所以priority仅用来使treap带有堆的性质
        //所以treap仍然是二叉搜索树
        //某个节点的priority必须必它的子节点的小 
        int_t priority;
        //同样值的个数,用于值会重复的情况
        int_t count = 1;
        //以该节点为根节点的子树的节点数(考虑重复值的情况)
        int_t size = 1;
        //左子节点
        Node* left = nullptr;
        //右子节点
        Node* right = nullptr;
        //父节点 注意 这个值只会在查询时更新
        Node* parent = nullptr;
    
        Node(int_t value) :
        value(value) {
            priority = rand();
        }
        //重新计算该节点的size值
        void maintain() {
            size = count;
            if (left) size += left->size;
            if (right) size += right->size;
        }
        //返回当前节点左子节点的size 即比当前节点小的节点的个数
        int_t leftSize() {
            if (left) return left->size;
            else return 0;
        }
    };
    Node* root = nullptr;
    //在treap中搜索一个节点,返回节点的指针,如果没找到就返回nullptr
    Node* lookup(Node* node, int_t value, Node* from = nullptr) {
        if (node == nullptr) {
            return nullptr;
        }
        //搜索的过程中顺便设置一下父节点
        node->parent = from;
        if (node->value == value) {
            return node;
        } else if (value < node->value) {
            return lookup(node->left, value, node);
        } else {
            return lookup(node->right, value, node);
        }
    }
    //把node的右子节点绕node左旋
    void leftRotate(Node*& node) {
        //把node的右节点存起来
        Node* temp = node->right;
        //然后把node的右节点换成tmp的左节点
        node->right = temp->left;
        //把temp的左节点换成node
        temp->left = node;
        //然后重新算一下size值
        node->maintain();
        temp->maintain();
        node = temp;
    
    }
    //把node的左子节点绕node右旋
    
    void rightRotate(Node* & node) {
        Node* temp = node->left;
        node->left = temp->right;
        temp->right = node;
        node->maintain();
        temp->maintain();
        node = temp;
    
    }
    //向以node为根节点的子树中插入value 并且维护最小堆
    void insert(Node*& node, int_t value) {
    
        if (node == nullptr) {
            //如果node==nullptr就说明应该在这个位置插入节点
            node = new Node(value);
        } else {
            //向左子树插入
            if (value < node->value) {
                insert(node->left, value);
                //插入结束后,如果插入的节点破坏了最小堆的性质,就需要旋转一下,把优先级小的节点转到上面
                //这样做不会破坏二叉搜索树的性质
                if (node->left->priority > node->priority) {
                    rightRotate(node);
                }
    
            } else if (value > node->value) {
                insert(node->right, value);
                if (node->right->priority > node->priority) {
                    leftRotate(node);
                }
    
            } else {
                //特殊情况 如果value已经存在于树中 则直接在计数器+1
                node->count++;
            }
        }
        //重新算一下size
        if (node) node->maintain();
    }
    //从以node为根节点的子树中删除value
    
    void remove(Node* & node, int_t value) {
    //找到了要删除的节点
        if (node->value == value) {
            //特殊情况 如果有多个值相同的节点 那么计数器-1即可
            if (node->count > 1) {
                node->count--;
                
            } else if (node->left == nullptr) {
                //如果当前节点没有左子节点 那么就直接把右子节点接上来
                Node* bak = node->right;
                delete node;
                node = bak;
            } else if (node->right == nullptr) {
                Node* bak = node->left;
                delete node;
                node = bak;
            } else {
                //如果node有两棵子树,那么就把优先级小的那一棵旋转到node,然后在另一个子树(此时node在这个子树里)删除node
                //执行完多次旋转后,最后一定会到node只有一棵子树或者没有子树的情况
                if (node->left->priority < node->right->priority) {
                    rightRotate(node);
                    remove(node->right, value);
                } else {
                    leftRotate(node);
                    remove(node->left, value);
                }
            }
        } else {
            if (value < node->value) {
                remove(node->left, value);
            } else {
                remove(node->right, value);
            }
        }
        if (node) node->maintain();
    }
    //输出先序遍历
    void preOrder(Node* node) {
        if (node == nullptr) return;
        cout << node->value << endl;
        preOrder(node->left);
        preOrder(node->right);
    }
    //输出中序遍历
    void inOrder(Node* node) {
        if (node == nullptr) return;
        inOrder(node->left);
        cout << node->value << endl;
    
        inOrder(node->right);
    }
    //输出后序遍历
    void postOrder(Node* node) {
        if (node == nullptr) return;
        postOrder(node->left);
        postOrder(node->right);
        cout << node->value << endl;
    }
    //求某个节点的前驱 即比这个节点小的最大的节点
    //返回该节点指针 若不存在则返回nullptr
    //需要保证value一定存在于树中
    Node* pre(Node* node, int_t value) {
        node = lookup(node, value);
        if (node->left) {
            //如果node有左子节点 那么node的前驱一定是node的左子节点的最右边的节点
            node = node->left;
            while (node->right) node = node->right;
            return node;
        } else {
            //如果node没有左子节点 则顺着node一直往上找 直到找到node是某个节点的左子节点为止
            Node* parent = node->parent;
            while (parent && node != parent->right) {
                node = parent;
                parent = parent->parent;
            }
            return parent;
        }
    }
    //求节点的后继 与前驱完全对称 不再赘述
    Node* post(Node* node, int_t value) {
        node = lookup(node, value);
        if (node->right) {
            node = node->right;
            while (node->left) node = node->left;
            return node;
        } else {
            Node* parent = node->parent;
            while (node && node != parent->left) {
                node = parent;
                parent = parent->parent;
    
            }
            return parent;
        }
    }
    //在以node为根节点的子树中查询value的排名
    //要求value必须存在于树中
    int_t rank(Node* node, int_t value) {
        if (node == nullptr) return 0;
        //如果value恰好等于node->value 就说明node的左子树的所有节点恰好比value小 node的右子树的节点恰好比value 大 value的名次就是node左子树的节点数
        if (value == node->value) {
            return node->leftSize();
        } else if (value < node->value) {
            //如果value比node->value小 那么就应该去node的左子树确定value的名次
            return rank(node->left, value);
        } else {
            //如果value比node大 那么value的名次就是 node左子树的大小+值为node->value的元素个数+value在右子树的排名
            return rank(node->right, value) + node->count + node->leftSize();
        }
    }
    //在以node为根的子树内找第k小的元素
    Node* kth(Node* node, int_t k) {
        if (node == nullptr || k < 1 || k > node->size) return nullptr;
        //如果k恰好是node的名次
        //因为同一个元素可能有多个 所以需要这样写
        if (node->leftSize() + 1 <= k && k <= node->leftSize() + node->count) {
            return node;
        } else if (k <= node->leftSize()) {
            return kth(node->left, k);
        } else {
            return kth(node->right, k - node->leftSize() - node->count);
        }
    }
    
    int main() {
    //    freopen("Z:/data.in", "r", stdin);
    //    freopen("Z:/data.out", "w", stdout);
    
        int_t n;
        cin>>n;
        while (n--) {
            string opt;
            cin>>opt;
            if (opt == "insert" || opt == "1") {
                int_t x;
                cin>>x;
                insert(root, x);
            } else if (opt == "find") {
                int_t x;
                cin>>x;
                cout << lookup(root, x) << endl;
            } else if (opt == "remove" || opt == "2") {
                int_t x;
                cin>>x;
                remove(root, x);
            } else if (opt == "pre") {
                preOrder(root);
            } else if (opt == "in") {
                inOrder(root);
            } else if (opt == "post") {
                postOrder(root);
            } else if (opt == "prev" || opt == "5") {
                int_t x;
                cin>>x;
                insert(root, x);
                cout << pre(root, x)->value << endl;
                remove(root, x);
            } else if (opt == "next" || opt == "6") {
                int_t x;
                cin>>x;
                insert(root, x);
                cout << post(root, x)->value << endl;
                remove(root, x);
            } else if (opt == "rank" || opt == "3") {
                int_t x;
                cin>>x;
                insert(root, x);
                cout << rank(root, x) + 1 << endl;
                remove(root, x);
            } else if (opt == "kth" || opt == "4") {
                int_t x;
                cin>>x;
                Node* node = kth(root, x);
                if (node)
                    cout << node->value << endl;
                else cout << 0 << endl;
            }
        }
    }
    
  • KMP字符串匹配算法

    KMP字符串匹配算法可以理解为一种经过优化的暴力匹配。

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    
    int_t nxt[1000000 + 1];
    
    void getNext(string pattern) {
        nxt[0] = -1;
        int_t k = -1;
        int_t j = 0;
        while (j < pattern.length()) {
            if (k == -1 || pattern[j] == pattern[k]) {
                if (pattern[++j] == pattern[++k]) {
                    nxt[j] = nxt[k];
                } else {
                    nxt[j] = k;
                }
            } else {
                k = nxt[k];
            }
        }
    }
    
    int main() {
        srand(time(0));
        string pattern, source;
        memset(nxt, 0, sizeof (nxt));
        int_t x1, x2;
        cin >> x1>>x2;
        for (int_t i = 0; i < x1; i++) {
            source = source + char(rand() % 5 + 'A');
        }
        for (int_t i = 0; i < x2; i++) {
            pattern = pattern + char(rand() % 5 + 'A');
        }
        cout << source << endl << pattern << endl;
        int_t i = 0, j = 0;
        getNext(pattern);
        while (i < source.length() && j < (signed long long) pattern.length()) {
            //cout << "x:i=" << i << " j=" << j << endl;
            if (j == -1 || source[i] == pattern[j]) {
                //cout << "i=" << i << " j=" << j << " ok" << endl;
                i++;
                j++;
            } else {
                //  cout << "j=" << j << " to " << nxt[j] << endl;
                j = nxt[j];
            }
            if (j == pattern.length()) {
                cout << i - j + 1 << endl;
                i = i - j + 2;
                j = 0;
            }
            // cout << "y:i=" << i << " j=" << j << endl;
            ///  cout << (i < source.length()) << " " << (j < (signed long long )pattern.length()) << endl;
        }
        for (int_t i = 0; i < pattern.length(); i++) cout << nxt[i] << " ";
        cout << endl;
        main();
        return 0;
    }
    
  • Trie树

    Trie树是一种用来保存字符串集合的树。

    Trie树可在$$O(n)$$的时间内将一个字符串插入该集合中,也可以在$$O(n)$$的时间内确定一个字符串是否在集合中。

    这是一颗Trie树(图自https://baike.baidu.com/pic/%E5%AD%97%E5%85%B8%E6%A0%91/9825209/0/9252ae7e96e893610dd7da67?fr=lemma&ct=single#aid=0&pic=9252ae7e96e893610dd7da67)

    每i层的节点存储字符串中的第i个字符。

    代码实现:

     

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = unsigned long long int;
    
    struct TrieNode {
        TrieNode* chd[26];
        int_t value = 0;
    
        TrieNode() {
            memset(chd, 0, sizeof (chd));
        }
    };
    TrieNode* root = new TrieNode;
    
    void insert(string str, int_t v) {
        TrieNode * ptr = root;
        for (int_t i = 0; i < str.length(); i++) {
            if (ptr->chd[str[i] - 'a'] == nullptr) {
                ptr->chd[str[i] - 'a'] = new TrieNode;
            }
            ptr = ptr->chd[str[i] - 'a'];
        }
        ptr->value = v;
    
    }
    vector vec;
    
    void map(TrieNode * v) {
        if (v->value) {
            for (char chr : vec) cout << chr;
            cout << endl;
        }
        for (int_t i = 0; i <= 'z' - 'a'; i++) {
            if (v->chd[i]) {
                vec.push_back(i + 'a');
                map(v->chd[i]);
                vec.pop_back();
            }
        }
    }
    
    bool query(string str) {
        TrieNode * ptr = root;
        for (int_t i = 0; i < str.length(); i++) {
            if (ptr->chd[str[i] - 'a'] == nullptr) {
                return false;
            }
            ptr = ptr->chd[str[i] - 'a'];
        }
        if (ptr->value == 0) return false;
        return true;
    
    }
    
    int main() {
        while (true) {
            string opt;
            cin>>opt;
            if (opt == "exit") break;
            else if (opt == "insert") {
                string str;
                cin>>str;
                insert(str, 1);
            } else if (opt == "query") {
                string str;
                cin>>str;
                cout << query(str) << endl;
            } else if (opt == "map") {
                map(root);
            }
        }
    
        return 0;
    
    }
    
  • 快速幂与矩阵快速幂

    首先介绍快速幂:

    快速幂可以在常数时间内计算$$a^b$$,而朴素的算法则需要$$O(b)$$

    思想:将b转换为二进制,则

    $$b=1+2+4+8+……$$

    $$a^b=a^{1}*a^{2}*a^{4}*….=a^{1}*(a^{1})^{2}*((a^{1})^{2})^{2}*…$$

    例如:

    求$$3^5$$

    $$\because 5=1+4$$

    $$\therefore 3^5=3^{1+4}=3^1*3^4$$

    先计算$$3^1=3$$,结果乘上3 然后计算$$(3^1)^2=3^2=9$$,然后计算$$(3^2)^2=3^4=81$$,结果乘上81,最终结果为$$3*81=243$$

    代码:

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    
    int_t fastPower(int_t base, int_t index) {
        //结果
        int_t result = 1;
        //只要指数不为0就一直循环
        while (index) {
            //index的最低位为1,结果中应该乘上base的对应次幂
            if (index & 1) {
                result = result*base;
            }
            base = base*base;
            //index右移一位
            index >>= 1;
        }
        return result;
    }
    
    int main() {
        int_t base, index;
        while (true) {
            cin >> base>>index;
            if (base == 0 && index == 0) break;
            cout << fastPower(base, index) << endl;
    
        }
        return 0;
    }
    

    矩阵快速幂:

    使用快速幂的思想来计算矩阵的幂

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = unsigned long long int;
    const int_t mod = (int_t) 1e9 + 7;
    //Matrix的前置声明,用于下面两个函数
    template 
    class Matrix;
    //友元函数的声明
    //模板类的友元函数必须显式的写明模板
    template 
    ostream& operator<<(ostream & os, const Matrix & mat);
    template 
    istream& operator>>(istream & os, const Matrix & mat);
    
    template 
    class Matrix {
    private:
        ValType* data;
        int_t row;
        int_t col;
        int_t size;
        int_t num;
    public:
        //析构函数,释放内存
        ~Matrix() {
            delete[] data;
        }
        //拷贝构造函数
        Matrix(const Matrix& other) :
        row(other.row), col(other.col), size(other.size), num(other.num) {
            data = new ValType[other.num];
            memcpy(data, other.data, other.size);
        }
        //默认构造函数,动态分配内存
        Matrix(int_t r, int_t c) :
        row(r), col(c) {
            this->row = r;
            this->col = c;
            num = (row + 1)*(col + 1);
            data = new ValType[num];
            size = sizeof (ValType) * num;
            memset(data, 0, size);
        }
        //访问矩阵的某个元素
        ValType& at(int_t r, int_t c) {
            return data[c + r * col];
        }
        //重载的*运算符,矩阵无法相乘时会抛出异常
        Matrix operator*(Matrix& mat) throw (const char*) {
            if (this->col != mat.row) throw "Column number of matrix1 doesn't equals that in matrix2";
            Matrix result(this->row, mat.col);
            for (int_t i = 1; i <= this->row; i++) {
                for (int_t j = 1; j <= mat.col; j++) {
                    int_t sum = 0;
                    for (int_t x = 1; x <= this->col; x++) {
                        sum = (sum % mod + at(i, x) % mod * mat.at(x, j) % mod) % mod;
                    }
                    result.at(i, j) += sum % mod;
                }
            }
            return result;
        }
    
        int_t getCol() const {
            return col;
        }
    
        int_t getRow() const {
            return row;
        }
        //赋值构造函数
        Matrix & operator=(const Matrix & other) {
            this->col = other.col;
            this->num = other.num;
            this->row = other.row;
            this->size = other.size;
            this->data = new ValType[num];
            memcpy(data, other.data, size);
        }
        //友元,函数名后必须写<>以告诉编译器这个友元使用了模板,如果不能从参数中推断模板类型那么还必须在<>内写上模板类型
        friend ostream& operator<< <>(ostream & os, const Matrix & mat);
        friend istream& operator>> <>(istream & os, const Matrix & mat);
    };
    //友元函数的实现
    template 
    ostream & operator<<(ostream & os, Matrix & mat) {
        for (int_t i = 1; i <= mat.getRow(); i++) {
            for (int_t j = 1; j <= mat.getCol(); j++) {
                os << mat.at(i, j) % mod << " ";
            }
            os << endl;
        }
        return os;
    }
    
    template 
    istream & operator>>(istream & is, Matrix & mat) {
        for (int_t i = 1; i <= mat.getRow(); i++) {
            for (int_t j = 1; j <= mat.getCol(); j++) {
                is >> mat.at(i, j);
                mat.at(i, j) %= mod;
            }
        }
        return is;
    }
    using MatType = Matrix;
    
    int main() {
    
    
        int_t n, index;
        cin >> n>>index;
        MatType mat(n, n);
        cin>>mat;
        MatType result = mat;
        //普通的快速幂
        for (index--; index; index>>=1,mat = mat * mat) {
            if (index & 1) result = result * mat;
        }
        cout << result;
        return 0;
    }
    

    此外,矩阵$$A=\begin{bmatrix}
    0&1 \\
    1&1
    \end{bmatrix}$$的n次方所得的矩阵的对角线上的元素即为斐波那契数列的第n项。

  • 乘法逆元

    如果$$ax\equiv 1(mod n)$$则称x为a模n意义下的逆

    则有$$ax-ny=1$$,然后用扩展欧几里得算法求解即可。

  • 唯一分解定理

    任何一个正整数都可以写成几个质数的幂次方之积,即
    $$n={p_1}^{k_1} {p_2}^{k_2} {p_3}^{k_3} {p_4}^{k_4} …..{p_m}^{k_m} $$ 其中$$p_m$$为素数,$$k_m$$为非负整数

    例如

    $$30=2^1*3^1*5^1$$

    $$40=5*8=5^{1}*2^{3}$$

    $$72=2*36=2^1*6^2$$

    代码实现:

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    
    map func(int_t x) {
        //map
        //x=p1^k1*p2^k2...........pn^kn
        map result;
        for (int_t i = 2; i <= x; i++) {
            //如果x的可以整除i,则结果中i的指数+1
            while (x % i == 0) {
                result[i]++;
                x /= i;
            }
        }
        return result;
    }
    
    int main() {
        int_t x;
        while (cin >> x) {
            auto result = func(x);
            cout << x << " = ";
            //请手动忽略最后多余的乘号
            for (auto p : result) {
                cout << p.first << "^" << p.second << " * ";
            }
            cout << endl;
        }
    }
    
  • 欧拉函数

    设函数$$\phi (x)$$为不超过x且与x互素的正整数的个数(两个数互素,则两个数的最大公因数为1)

    则$$\phi(x)=x(1-\frac {1} {p_1})(1-\frac {1} {p_2})(1-\frac {1} {p_3})(1-\frac {1} {p_4})·····(1-\frac {1} {p_n})$$

    其中$$p_n$$为将x通过唯一分解定理所得的式子的底数,即$$n={p_1}^{k_1} {p_2}^{k_2} {p_3}^{k_3} {p_4}^{k_4} …..{p_m}^{k_m} $$ 其中$$p_n$$为素数

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    
    int_t func(int_t x) {
        int_t result = x;
        for (int_t i = 2; i <= x; i++) {
            //如果x的可以整除i,则结果中i的指数+1
            if (x % i == 0) {
                //    result = result * (i - 1) / i;
                //先乘后除防止溢出
                result = result / i * (i - 1);
                while (x % i == 0) x /= i;
            }
        }
        if (x > 1) result = result / x * (x - 1);
        return result;
    }
    
    int main() {
        int_t x;
        while (cin >> x) {
            int_t result = func(x);
            cout<
    

    或者可以通过类似于线性筛素数的方式求出一定范围内所有的数的欧拉函数值

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    using int_t = long long int;
    const int_t MAX = 100000;
    int_t phi[MAX + 1];
    int main() {
        int_t x;
        cin>>x;
        memset(phi, 0, sizeof (phi));
        phi[1] = 1;
        for (int_t i = 2; i <= x; i++) {
            if (phi[i] == 0) {
                for (int_t j = i; j <= x; j = j + i) {
                    if (phi[j] == 0) phi[j] = j;
                    phi[j] = phi[j] / i * (i - 1);
                }
            }
    
        }
        for(int_t i=1;i<=x;i++) cout<<"phi("<<i<<") = "<<phi[i]<<endl;
    
    }
    
  • 线性筛素数

    使用欧拉筛法可以接近线性的时间内筛出一定范围内所有素数。

    基本思想:如果$$a$$是素数,那么$$a*1,a*2,a*3,a*4,a*5…..$$都不是素数,这样就可以对一定范围内的非素数进行标记。

    代码:

     

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    const int_t MAX = 10000;
    char isPrime[MAX + 1];
    vector primeTable;
    
    int main() {
        //初始时,假设所有数都是素数
        memset(isPrime, 1, sizeof (isPrime));
        int_t n;
        cin>>n;
        //1当然不是素数
        isPrime[1] = 0;
        //i只需要枚举到sqrt(n),因为i>sqrt(n)的情况已经被考虑过了
        for (int_t i = 2; i <= sqrt(n + 0.5); i++) {
            //当外层循环执行到i时,如果i仍未被标记非素数,就说明i真的是素数
            //如果i不是素数,那样就不需要继续标记i*2,i*3,i*4....了 因为这些情况已经被标记了
            if (isPrime[i]) {
                for (int_t j = i * i; j <= n; j =j+i) {
                    isPrime[j] = false;
                }
            }
        }
        for (int_t i = 1; i <= n; i++) {
            if (isPrime[i]) {
                primeTable.push_back(i);
            }
        }
        for (int_t prime : primeTable) {
            cout << prime << endl;
        }
    
        return 0;
    }
    
  • 求最长上升子序列的O(n log n)算法

    上升子序列:从一个序列中按照顺序挑出一些数,使得后一个数大于前一个数

    例如对于序列$$1,5,3,7,7,2$$

    $$1,5,7$$ $$5,7$$ $$1,3$$都是其上升子序列

    但是$$1,5,7,7$$不是上升子序列(不满足后一个数大于前一个数)

    最长上升子序列:一个序列中最长的上升子序列

    对于序列$$1,5,3,7,7,2$$ 其最长上升子序列是$$1,3,7$$或者$$1,5,7$$

     

    求最长上升子序列的算法

    1.朴素的O(n²)的动态规划

    设dp[tail]为序列中以第tail个元素结尾的最长上升子序列的长度

    则有dp[1]=1 (第一个数自成一个最长上升子序列)

    tail>1时$$dp[tail]=max\{0,dp[1],dp[2],dp[3]…dp[i]…dp[tail-1]|满足seq[i]<seq[tail]\}+1$$

    结果为$$max{dp[i]|1<=i<=序列长度}$$

    解释:求dp[tail]时,从序列中tail前的所有数中找到比seq[tail]小的数,在满足这个数比seq[tail]小的情况下使dp[tail]最大

    举例:对于序列

    $$1,5,3,2,5,6$$

    使用seq[1]表示序列中的第一项,seq[2]表示序列中的第二项,以此类推

    初始时dp[1]=1;

    然后开始求dp[2] 发现seq[1]<seq[2] 所以dp[2]=dp[1]+1=2

    开始求dp[3] 发现seq[1]<seq[3] 更新dp[3]=dp[1]+1=2

    开始求dp[4] 发现seq[1]<seq[4] 更新dp[4]=dp[1]+1=2;

    开始求dp[5] 发现seq[1]<seq[5] 更新dp[5]=dp[1]+1=2

    又发现seq[3]<seq[5] 再次更新dp[5]=dp[3]+1=3

    开始求dp[6] 发现seq[1]<seq[6] 更新dp[6]=dp[1]+1=2

    又发现seq[2]<seq[6] 更新dp[6]=dp[2]+1=3

    又发现seq[3]<seq[6] 更新dp[6]=dp[3]+1=3

    以此类推

     

    最终 dp[1]=1 dp[2]=2 dp[3]=2 dp[4]=2 dp[5]=3 dp[6]=3

     

    最终结果为3

     

    程序:

    #include 
    
    using namespace std;
    typedef long long int_t;
    int_t seq[1001];
    int_t dp[1001];
    
    int main() {
        dp[1] = 1;
        int_t n;
        cin>>n;
        for (int_t i = 1; i <= n; i++) cin >> seq[i];
        //一次性计算出LIS
        for (int_t i = 1; i <= n; i++) {
            int_t length = 0;
            for (int_t j = 1; j <= i; j++) { if (seq[i] > seq[j]) {
                    if (dp[j] > length) length = dp[j];
                }
            }
            dp[i] = length + 1;
    
        }
        int_t result = 0;
        for (int_t i = 1; i <= n; i++) {
            result = max(result, dp[i]);
        }
        cout << result;
        return 0;
    }
    

    2.复杂度为O(n log n)的算法

    设dp[i]为以序列中第i个元素结尾的最长上升子序列的长度,那么如果对于序列中任意两个数seq[i]和seq[j] 如果满足dp[i]==dp[j]且seq[i]<seq[j] 那么仅保存i一定不会丢失最优解(因为seq[i]<seq[j],所以可以接到seq[j]后面的数一定可以接到seq[i]后面,然而可以接到seq[i]后面的数却不一定能接到seq[j]后面)

    设g[x]为满足dp[i]==x的最小的seq[i]

    下一句的最长上升子序列长度是指以该数结尾的最长上升子序列的长度

    例:g[3]为满足最长上升子序列长度为3的序列中最小的数

    对于以下序列$$1,5,3,6,7$$

    $$

    g[1]=1

    g[2]=3 (不能是5,因为3比5小)

    g[3]=6

    g[4]=7

    g[5]=\infty (因为不存在长度的5的最长上升子序列)

    $$

    同时一定满足

    $$g[1]<=g[2]<=g[3]<=g[4]……..<=g[n]$$

    所以为了确定序列中的一个数seq[i]的最长上升子序列长度,只需要在g数组中进行二分查找即可。查找后,查找结果即为以seq[i]结尾的最长上升子序列的长度

    但要注意:

    假设在g中二分查找seq[i]所得到的结果是index,那么还需要把g[index]改为seq[i],以此维护g数组的单调性(因为seq[i]一定比g[index]小,如果seq[i]比g[index]大,那么二分查找的结果就不是index了)

    代码:注意,初始时g数组需要全部设置为$$\infty$$

     

     

    #include 
    #include 
    #include 
    using namespace std;
    using int_t = long long int;
    int_t seq[100000 + 1];
    int_t g[100000 + 1];
    int_t result[100000 + 1];
    
    int main() {
        int_t n;
        cin>>n;
        for (int_t i = 1; i <= n; i++) cin >> seq[i];
        memset(g, 0x7f, sizeof (g));
    
        for (int_t i = 1; i <= n; i++) {
            int_t index = lower_bound(g + 1, g + 1 + n, seq[i]) - g;
            result[i] = index;
            g[index] = seq[i];
        }
        int_t r = 0;
        for (int_t i = 1; i <= n; i++) r = max(r, result[i]);
        cout << r;
        return 0;
    }
    

    因为二分查找的复杂度为$$O(log n)$$,所以该算法的复杂度为$$O(n log n)$$

     

    两种算法的效率对比:

    数据量为10000时:

     
    每组数据第一行为算法2,第二行为算法1

    1:
            0
            0.672
    2:
            0
            0.656
    3:
            0.016
            0.641
    4:
            0.015
            0.656
    5:
            0.016
            0.641
    6:
            0.015
            0.719
    7:
            0
            0.656
    8:
            0.016
            0.609
    9:
            0
            0.687
    10:
            0.016
            0.672
    
    

    数据量为100000时

    1:
            0.094
            65.765
    2:
            0.093
            63.454
    3:
            0.078
            65.172
    4:
            0.093
            65.391
    5:
            0.079
            61.999
    6:
            0.094
            63.703
    7:
            0.094
            65.047
    8:
            0.094
            65.124
    9:
            0.109
            64.954
    10:
            0.062
            63.766
    
    可以看到,n log n的算法明显要比n^2的算法快
    
  • 欧几里得算法与扩展欧几里得算法

    欧几里得算法,即人教版高中数学必修三上的辗转相除法

    用于求两个整数的最大公因数(即gcd(a,b))
    注:a b的最小公倍数等于$$\frac {ab}{\gcd \left(a,b\right)}$$

    int gcd(int a, int b) {
        //边界b==0 此时a就是结果
        if (b == 0) {
            return a;
        } else {
            return gcd(b, a % b);
        }
    }
    

    扩展欧几里得算法:在求最大公约数的同时求出方程$$ax+by=gcd(a,b)$$的一组整数解,使|x|+|y|最小

    //a b 方程中的a b 
    //gcd 用来存放gcd(a,b)
    //x y 用来存放解
    void exgcd(int a, int b, int & gcd, int& x, int& y) {
        //边界b==0 此时a就是结果
        if (b == 0) {
            x = 1;
            y = 0;
            gcd = a;
        } else {
            exgcd(b, a % b, gcd, y, x);
            y -= (a / b) * x;
    
        }
    }
    

    求出$$ax+by=gcd(a,b)$$的解之后,如果$$c$$可以整除$$gcd(a,b)$$ 就说明方程$$ax+by=c$$存在整数解$$x_0,y_0$$,而且这组解为$$x_1=x_0*c/gcd(a,b),y_1=y_0*c/gcd(a,b)$$

    然后,如果$$x_1,y_1$$是方程$$ax+by=c$$的解,则$$x_1+k*a/gcd(a,b),y_1-k*b/gcd(a,b)$$都是方程$$ax+by=c$$的解