zoukankan      html  css  js  c++  java
  • 刷题周记(一)——最长连续不重复子序列 、数的三次方根、差分 、前缀和、最大异或对、快速排序、子矩阵的和、差分矩阵、区间和、区间合并、单链表、双链表、模拟栈、模拟队列、单调栈、滑动窗口(单调队列)

    ————————————————————2020年10月25日(周日)

    一、最长连续不重复子序列

    首先要明白,子序列不一定要所有元素挨在一起
    == 子串当然就是要再在一起啦==,就像一串糖葫芦一样,被在一起了。

    但是,这道题所谓的“最长连续子序列”其实是“子串”啦。
    题目
    思路注释

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e6 + 10;
    int n;
    int a[N];
    bool s[N];
    int cnt = 0, t = 0;
    
    int main(){
            memset(s, 0, sizeof s);
        cin >> n;
        for(int i = 0; i < n; i ++)
            cin >> a[i];
            
        int j = -1, i;
        for(i = 0; i < n; i ++){//i不断往前走
        	//每一轮都要找到从i开始的最长不重复子序列
            // for(int k = 0; k < 20; k ++) cout << s[k] << ' ';
            //     cout << endl;
            if(t > 0)//如果当前有长度,往前走就要长度-1
                t --;  
            while(j + 1 < n && !s[a[j + 1]]){
                s[a[j + 1]] = 1;
                t ++;
                j ++;
            }
                // cout << i << endl;
            s[a[i]] = 0;
            cnt = max(cnt, t);
            // cout << "cnt :" << cnt << endl;
        }
       
        cout << cnt;
        return 0;
    }
    

    二、数的三次方根

    题目
    思路:不断二分找答案,然后记得小数要判断精度!

    #include <iostream>
    using namespace std;
    int main(){
        double x;
        scanf("%lf", &x);
        double l = -100, r = 100;
        while(r - l > 1e-8){
            double mid = (l + r) / 2;
            if(mid * mid * mid >= x) r = mid;
            else l = mid;
        }
        printf("%.6lf", r);
        return 0;
    }
    

    三、差分

    题目

    思路:
    将要加上某个数的区间第一个数加上要加的数,
    区间末后一个元素减去此元素
    就可以使区间内的数都加上此元素而后面的数保持原样

    #include<bits/stdc++.h>
    #define MAXN 200005
    using namespace std;
    
    int n,m,a[MAXN],b[MAXN];
    
    int main(){
        cin >> n >> m;
        for(int i = 1; i <= n; i ++) cin >> a[i];
        int l, r, c;
        for(int i =1 ; i <= m; i ++){
            cin >> l >> r >> c;
            b[l] += c; b[r + 1] -= c;
        }
        for(int i = 1; i <= n ;i ++) b[i] += b[i - 1];
        for(int i = 1; i <= n; i ++) cout << a[i] + b[i] << " ";
        return 0;
    }
    

    四、前缀和

    转到原题

    #include <iostream>
    
    using namespace std;
    
    const int N = 100010;
    
    int n, m;
    int a[N], s[N];
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
        for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i]; // 前缀和的初始化
        while(m --){//这是第一种输入输出方式
            int l, r;
            cin >> l >> r;
            cout << s[r] - s[l - 1] << endl;
        }
    
        while (m -- )//这是第二种输入输出方式
        {
            int l, r;
            scanf("%d%d", &l, &r);
            printf("%d
    ", s[r] - s[l - 1]); // 区间和的计算
        }
    
    
        return 0;
    }
    

    两种输入输出时间对比
    可以看到时间差了很远很远!!!,所以输入一定要用scanf !

    ————————————————————2020年10月26日(周一)

    一、最大异或对

    传送
    整理博客时发现的,之前没仔细写思路,现在一开始自己也没有想起来之前怎么做的 (滑稽)

    (过了一周后再回来看,发现又忘记怎么做了……我的记忆果然是假的。——一周后的我

    注:-1的二进制码全是 1,所以 ~-1 == 0 !

    答案

    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int N = 100010, M = 3000000;
    
    int n;
    int a[N], son[M][2], idx;
    
    void insert (int x){
        int p = 0;
        for(int i = 30; ~i; i --){
            int s = x >> i & 1;
            if(!son[p][s]){
                son[p][s] = ++idx;
                p = idx;
            }
            else p = son[p][s];
        }
    }
    //这个函数就是说,将这个数分解为一个二进制数,然后按照这个二进制不断地往下开辟一条道路
    //每到一个新节点就给它取一个新的名字,这样往下找儿子的时候就不会产生混乱……
    int find(int x){
        int p = 0, res = 0;
        for(int i = 30; i >= 0; i --){
            int s = x >> i & 1;
            if(son[p][!s]){
                res += 1 << i;
                p = son[p][!s];
            }
            else p = son[p][s];
        }
        return res;
    }
    
    int main(){
        scanf("%d", &n);
        for(int i = 0; i < n; i ++){
            scanf("%d", &a[i]);
            insert(a[i]);
        }
        
        int res = 0;
        for(int i = 0; i < n; i ++) res = max(res, find(a[i]));
        
        cout << res ;
        return 0;
    }
    

    二、快速排序

    传送
    没啥好说的,对着模板打吧,边界问题一直是个迷……反正也很少用得着手写。(除了练习的时候,谁会手打这玩意儿?)

    #include <iostream>
    using namespace std;
    const int N = 100010;
    int q[N];
    void quick_sort(int l, int r)
    {
        if (l >= r) return;
    
        int i = l - 1, j = r + 1, x = q[l + r >> 1];
        while (i < j)
        {
            do i ++ ; while (q[i] < x);
            do j -- ; while (q[j] > x);
            if (i < j) swap(q[i], q[j]);
        }
        quick_sort(l, j);
        quick_sort(j + 1, r);
    }
    int main(){
        int n;
        cin >> n;
        for(int i = 0; i < n; i ++) scanf("%d ", &q[i]);
        quick_sort(0, n - 1);
        for(int i = 0; i < n; i ++) printf("%d ", q[i]);
        return 0;
    }
    

    ————————————————————2020年10月27日(周二)

    一、子矩阵的和(二维前缀和)


    思路:

    s[i][j] += ( s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] ) ;
    这个是将输入后的数组进行初始化,代表的是以(1, 1)为左上角以(i, j)为右下角的原矩阵的所有元素之和。
    s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]
    这句话是求规定范围(以(x1, y1)为左上角,以(x2, y2)为右下角)内的矩阵的所有元素之和。

    答案

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1010;
    int n, m, q;
    int s[N][N];
    
    int main(){
        scanf("%d%d%d", &n, &m, &q);
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= m; j ++)
                scanf("%d", &s[i][j]);
                
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= m; j ++)
                s[i][j] += ( s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] ) ;
        
        for(int i = 1; i <= q; i ++){
            int x1, x2, y1, y2;
            scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
            printf("%d
    ", s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]);
        }
        return 0;
    }
    

    二、差分矩阵

    turn
    思路
    只有一个操作:

    
    void insert(int x1, int y1, int x2, int y2, int c){
        b[x1][y1] += c;
        b[x2 + 1][y1] -= c;
        b[x1][y2 + 1] -= c;
        b[x2 + 1][y2 + 1] += c;
    }
    

    就是这个insert函数,意思是 将这个矩阵之内的数全部加上c, 其余部分不会变。
    而最后的实现其实就是前面子矩阵的和这道题的方法,也就是将最后得到的b数组进行二维前缀和的操作,
    得到的b数组就是答案

    举例理解:

    	首先 假设 有一个4X4 的小方块要加上 2, 那么我们先将方块的左上角(1, 1)加上2。
     	然后你想,我们最后是要进行二维前缀和的运算的,
     	那么为了不让其它部分受到影响,我们要将(1, 5)(5, 1)上的数减去2,与从(1, 1)传递过来的2相互抵消了,这样它们后面的数也不会受到影响了。
     	
    	但是注意!有一个位置,那就是b(5, 5)它减去了两次2!
    	因为它同时受b(5, 1)b(1, 5)的影响,所以我们还要在b(5, 5)再加上2.
    

    答案

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1010;
    
    int n, m, q;
    int a[N][N], b[N][N];
    
    void insert(int x1, int y1, int x2, int y2, int c){
        b[x1][y1] += c;
        b[x2 + 1][y1] -= c;
        b[x1][y2 + 1] -= c;
        b[x2 + 1][y2 + 1] += c;
    }
    
    int main(){
        scanf("%d%d%d", &n, &m, &q);
        for(int i = 1; i <= n; i ++)
            for(int j = 1;j <= m; j ++){
                scanf("%d", &a[i][j]);
                insert(i, j, i, j, a[i][j]);
            }
            
        for(int i = 1; i <= q; i ++){
            int x1, x2, y1, y2, c;
            scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
            insert(x1, y1, x2, y2, c);
        }
        
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= m; j ++)
                b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1];
                
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++)
                printf("%d ", b[i][j]);
            puts("");
        }    
        return 0;
    }
    

    ————————————————————2020年10月28日(周三)

    一、区间和

    传送
    思路
    离散化(Discretization):把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。

    离散化 —— 模板题 AcWing 802. 区间和
    vector<int> alls; // 存储所有待离散化的值
    sort(alls.begin(), alls.end()); // 将所有值排序
    alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素
    
    // 二分求出x对应的离散化的值
    int find(int x) // 找到第一个大于等于x的位置
    {
        int l = 0, r = alls.size() - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (alls[mid] >= x) r = mid;
            else l = mid + 1;
        }
        return r + 1; // 映射到1, 2, ...n
    }
    
    作者:yxc
    链接:https://www.acwing.com/blog/content/277/
    来源:AcWing
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    传送
    答案
    总感觉这种写法太麻烦了 ……

    #include<bits/stdc++.h>
    using namespace std;
    typedef pair<int, int> P;
    const int N = 300010;
    int m, n;
    int a[N], s[N];
    vector<int> alls;
    vector<P> add, query;
    int find(int x){
    	int l = 0, r = alls.size() - 1;
    	while(l < r){
    		int mid = (l + r) >> 1;
    		if(alls[mid] >= x) r = mid;
    		else l = mid + 1;
    	}
    	return r;
    }
    vector<int>::iterator unique(vector<int> &a){
    	int j = 0;
    	for(int i = 0; i < a.size(); i ++)
    		if(!i || a[i] != a[i - 1])
    			a[j ++] = a[i];
        // a[0] ~ a[j - 1] 所有a中不重复的数
    	return a.begin() + j;
    	//返回end坐标
    }
    
    int main(){
    	scanf("%d%d", &n, &m);
    	//"首先进行 n 次操作,每次操作将某一位置x上的数加c。"
    	for(int i = 0; i < n; i ++){
    		int x, c;
    		scanf("%d%d", &x, &c);
    		add.push_back({x, c});
    		alls.push_back(x);
    	}
    	//"接下来,进行 m 次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和"
    	for(int i = 0; i < m; i ++){
    		int l, r;
    		scanf("%d%d", &l, &r);
    		query.push_back({l, r});
    		alls.push_back(l);
    		alls.push_back(r);
    	}
    	//去重
    	sort(alls.begin(), alls.end());
    	alls.erase(unique(alls), alls.end());
    	
    	// 处理插入
    	for(auto item : add){
    		int x = find(item.first);
    		a[x] += item.second;
    	}
    	
    	    // 预处理前缀和
    	for(int i = 1; i <= alls.size(); i ++)
    		s[i] = s[i - 1] + a[i];
    		
    		// 处理询问
    	for(auto item : query){
    		int l = find(item.first);
    		int r = find(item.second);
    		cout << s[r] - s[l - 1] << endl;
    	}
    	return 0;
    }
    

    ————————————————————2020年10月30日 (周五)

    一、区间合并

    传送
    思路

    区间合并 —— 模板题 AcWing 803. 区间合并
    // 将所有存在交集的区间合并
    void merge(vector<PII> &segs)
    {
        vector<PII> res;
    
        sort(segs.begin(), segs.end());
    
        int st = -2e9, ed = -2e9;
        for (auto seg : segs)
            if (ed < seg.first)
            {
                if (st != -2e9) res.push_back({st, ed});
                st = seg.first, ed = seg.second;
            }
            else ed = max(ed, seg.second);
    
        if (st != -2e9) res.push_back({st, ed});
    
        segs = res;
    }
    
    作者:yxc
    链接:https://www.acwing.com/blog/content/277/
    来源:AcWing
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    答案

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    typedef pair<int, int> PII;
    void merge(vector<PII> &segs){
        vector<PII> res;
        sort(segs.begin(), segs.end());
    
        int st = -2e9, ed = -2e9;
        for (auto seg : segs)
            if (ed < seg.first){//没有交集
                if (st != -2e9) res.push_back({st, ed});//也不是第一个,那就加入一个新的合成后的区间
                st = seg.first, ed = seg.second;//但是不论如何,st和ed都要重新开始
            }
            else ed = max(ed, seg.second);
        if (st != -2e9) res.push_back({st, ed});//有交集的话,比较一下谁的尾巴更长就用谁的尾巴,这就是合并
        segs = res;
    }
    
    int main(){
        int n;
        scanf("%d", &n);
    
        vector<PII> segs;
        for (int i = 0; i < n; i ++ ){
            int l, r;
            scanf("%d%d", &l, &r);
            segs.push_back({l, r});
        }
        merge(segs);
        cout << segs.size() << endl;
        return 0;
    }
    

    ————————————————————2020年10月31日(周六)

    数据结构模板

    一、单链表

    传送
    模板

    // head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
    int head, e[N], ne[N], idx;
    
    // 初始化
    void init(){
        head = -1;
        idx = 0;
    }
    
    // 在链表头插入一个数a
    void insert(int a){
        e[idx] = a, ne[idx] = head, head = idx ++ ;
    }
    
    // 将头结点删除,需要保证头结点存在
    void remove(){
        head = ne[head];
    }
    

    答案
    根据题目做了一些小改动,就是把nxt[0]看作是头结点,从而省略了head这个变量以及insert_head这个单独的函数。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 100010;
    int nxt[N], e[N];
    int idx = 1;
    void add (int n, int a){//在n位置之后加入元素a
        e[idx] = a, nxt[idx] = nxt[n], nxt[n] = idx ++;//注意,与头相反的方向是尾,也就是后面
    }
    void del(int k){
    	nxt[k] = nxt[nxt[k]]; 
    }
    int main(){
        int m;
        scanf("%d", &m);
        for(int i = 1; i <= m; i ++){
            char c;
            cin >> c;
            if(c == 'I'){
                int k, a;
                scanf("%d%d", &k, &a);
                add(k, a);
            }
            else if(c == 'D'){
                int k;
                scanf("%d", &k);
                del(k);
            }
            else if(c == 'H'){//注意:新插入的点是头
                int a;
                scanf("%d", &a);
                add(0, a);
            }
        }
        for(int i = nxt[0]; i ; i = nxt[i]) printf("%d ", e[i]);
        return 0;
    }
    

    二、双链表

    传送
    这个形象多了,个人觉得更好理解。
    在这里插入图片描述

    教训:if , else if 和 else一定要区分清楚,不然真的怎么出错都不知道!!

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 100010;
    int l[N], r[N], e[N], idx = 2;
    
    void insert(int k, int x){
        e[idx] = x;
        l[idx] = k, r[idx] = r[k];
        l[r[k]] = idx, r[k] = idx ++;
    }
    void del(int k){
        l[r[k]] = l[k];
        r[l[k]] = r[k];
    }
    int main(){
        r[0] = 1, l[1] = 0;
        int m;
        cin >> m;
        for(int i = 1; i <= m; i ++){
            string s;
            cin >> s;
            int k, x;
            if(s == "L"){
                scanf("%d", &x);
                insert(0, x);   
            }
            else if(s == "R"){
                scanf("%d", &x);
                insert(l[1], x);  
            } 
            else if(s == "D"){
                cin >> k;
                del(k + 1);//这里k + 1是因为我们是从2开始计算第一个节点的。  
            } 
            else if(s == "IL"){
                scanf("%d%d", &k, &x);
                insert(l[k + 1], x);    
            }
            else{
                scanf("%d%d", &k, &x);
                insert(k + 1, x);  
            } 
        }
        for(int i = r[0]; i != 1; i = r[i]) printf("%d ", e[i]);
        return 0;
    }
    

    三、模拟栈

    这种题真的有存在的必要吗……
    解答

    #include <iostream>
    using namespace std;
    const int N = 100010;
    int m;
    int stk[N], tt;
    int main()
    {
        cin >> m;
        while (m -- )
        {
            string op;
            int x;
            cin >> op;
            if (op == "push")
            {
                cin >> x;
                stk[ ++ tt] = x;
            }
            else if (op == "pop") tt -- ;
            else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
            else cout << stk[tt] << endl;
        }
        return 0;
    }
    

    四、模拟队列

    这个也是……
    解答

    #include <iostream>
    
    using namespace std;
    
    const int N = 100010;
    
    int m;
    int q[N], hh, tt = -1;
    
    int main()
    {
        cin >> m;
    
        while (m -- )
        {
            string op;
            int x;
    
            cin >> op;
            if (op == "push")
            {
                cin >> x;
                q[ ++ tt] = x;
            }
            else if (op == "pop") hh ++ ;
            else if (op == "empty") cout << (hh <= tt ? "NO" : "YES") << endl;
            else cout << q[hh] << endl;
        }
    
        return 0;
    }
    

    五、单调栈


    思路
    这里为什么用单调栈呢?
    因为如果输出第一个小于某数的数的话,那么每次一直往左找,直到找到最左边的那个,输出并记录。
    如果后来输入的数比它大,那就没有必要保留,类似出栈操作。
    这样可以很快地解决这种问题。

    #include <iostream>
    using namespace std;
    const int N = 100010;
    int stk[N], tt;
    
    int main()
    {
        int n;
        cin >> n;
        while (n -- )
        {
            int x;
            scanf("%d", &x);
            while (tt && stk[tt] >= x) tt -- ;
            if (!tt) printf("-1 ");
            else printf("%d ", stk[tt]);
            stk[ ++ tt] = x;
        }
    
        return 0;
    }
    

    六、滑动窗口(单调队列)

    传送
    思路
    经典的单调队列问题,三刷了……
    简单地说就死维护一个单调队列,每次输出队尾即可。
    梦幻联动(调试版也在里面啦)

    #include <iostream>
    using namespace std;
    const int N = 1000010;
    int a[N], q[N];
    int main()
    {
        int n, k;
        scanf("%d%d", &n, &k);
        for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
        
    //这里处理的是最小值
        int hh = 0, tt = -1//头 和 尾
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt    &&   i - k + 1 > q[hh] )  hh ++ ;
            //这里是当队列里面还有元素 以及 头不在这个窗口范围的时候,头就往后走一位
            while (hh <= tt &&   a[q[tt]] >= a[i]  )  tt -- ;
            //这里是要把队列中所有比 现在要插入的元素 大的元素出队
            //然后就会发现 现在队列里的元素 正是当前状态(加入新元素)的单调队列
            q[ ++ tt] = i;
    
            if (i >= k - 1) printf("%d ", a[q[hh]])//队头是最小的
        }
    
        puts("");
        
    //这里处理的是最大值
        hh = 0, tt = -1;
        for (int i = 0; i < n; i ++ )
        {
            if (hh <= tt && i - k + 1 > q[hh]) hh ++ ;
    
            while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;
            q[ ++ tt] = i;
    
            if (i >= k - 1) printf("%d ", a[q[hh]]);
        }
    
        puts("");
        
        return 0;
    }
    

    已完结 (标题满100了塞不下了) ,刚好一周!以后就一周算一章吧!就像日(周)记一样好了……

    什么嘛,这周只有16道题而已嘛,平均一天也就两道题多一点……

  • 相关阅读:
    [开源]用MQL4实现MD5加密
    如何转换WMV到MP3,WMV到MP3播放器
    C# Winform TreeView 的一些基本用法
    WinServer 2008 远程桌面连接设置
    存储数据类型的转化总结
    EF中执行sql语句,以及事务
    C#(委托a)
    LINQ绑定List到GridView
    循环遍历DataTable绑定到Table
    要将 ASP.NET 访问权限授予某个文件,请在资源管理器中右击该文件,选择“属性”,然后选择“安全”选项卡。单击“添加”添加适当的用户或组。突出显示 ASP.NET 帐户,选中所需访问权限对应的框。
  • 原文地址:https://www.cnblogs.com/yuanyulin/p/14026729.html
Copyright © 2011-2022 走看看