• 2017.8.11 数据结构课小结


    今天讲了并查集、堆和Hash表,并讲了几道比较难的题.

    例1.

    分析:其实这道题我们用一颗很普通的线段树,维护区间最大值就好了,因为最大值开方后还是最大值嘛.

    但是开方有一个比较重要的性质:一个10^7的数大概7,8次开方就是1了,以后不管怎么开方都是1,我们能不能不考虑这部分1呢?也就是说,我们能不能用一个数据结构跳过1呢?可以想到用双向链表,但是这样能不能做到线性呢?回想一下链表的操作,我们的第一步是要“找到”链表,也就是说我们要找到当前区间的第一个不是1的表头,这样其实很容易卡掉,考虑一组数据:21111112,如果我们当前处理的区间是[2,6],那么我们要寻找整个区间,复杂度O(nq),通不过所有测试点,能不能有一个数据结构,当我们处理到某个点时,能够跳到下一个非1的点呢?我们考虑这样做:

    如果这个点非1,那么就自己指向自己,否则指向下一个点,直到遇到自环,有没有感觉非常像并查集?

    其实我们只需要路径压缩一下,复杂度O(nlogn)。

       这样我们得到了做法:利用并查集暴力枚举每一个非1的数来修改,在修改的同时维护区间最值,最后利用线段树来查询.

    例2:传送门

    例3:

    分析:这道题目的意思是如果u,v的路径只有1种颜色,那么这种颜色的边能联通u,v.我们可以把每个颜色的边给“抽"出来,单独建一个图,然后利用并查集维护连通性,但是如果对于每个图都开一个数组,会有点炸内存啊,10^9的数组可不是说开就能开的,那么怎么做呢?其实可以发现有很多点是孤立的,我们把这些点给排除掉,最后剩下大概2m个点,可是这些点也很多啊,我们能怎么办呢?hash!

         因为我们用f[i][j]记录第i个图j的祖先嘛,我们可以用pair+map完美解决,也可以把i和j拼成一个字符串然后用字符串hash,解决一下hash碰撞就好了。

    例4:

    分析:这道题可以模拟,但是没啥用,因为过不了题......但是因为小球不标号,我们可以这么想:两个小球发生碰撞和两个小球对穿而过是等价的,于是每个球的答案就是xi + vi*t.

    例5:

    这个题意是错的,原题目为有标号的小球

    分析:先看前一组数据,考虑模拟,两个小球相碰撞的情况是方向相反且距离最近,我们把每两队小球碰撞的时间放进一个优先队列,每次处理时间最短的那一个,那么其它的几对小球相撞的时间就要减去第一队所花的时间,还要把新的相撞的小球对放进优先队列里,不过给每一个元素加上这个时间复杂度实在是太高了,我们可以考虑noip蚯蚓那道题的做法:我们把不需要处理的数加上我们原本要减去的数就好了,用一个变量记录我们到底要减去多少,到时候减回来就好了,这是第一组数据的做法.

         考虑第二组数据,我们利用例4的做法,我们先给每个小球以坐标大小为依据来编个序号,可以发现,最后下来,这个序号还是这个顺序,也就是说,一开始这个小球是第几个,那么之后他还是第几个,利用这个性质再利用例4的结论,就能A掉此题.

    例6:传送门

    例7:

    分析:因为断言没有矛盾,所以不可能出现一个好人或者坏人说自己是坏人的情况。我们现在考虑几种情况:1.如果A说B是好人,A是好人,那么B是好人。2.如果A说B是好人,A是坏人,那么B是坏人。3.如果A说B是坏人,A是坏人,那么B是好人。4.如果A说B是坏人,A是好人,那么B是坏人。可以看出:如果A说B是好人,不管A是什么,AB都是同类的,如果A说B是坏人,那么两人不同类。那么我们可以利用noip2010关押罪犯的处理方式来用并查集存和自己同一类的,不同类的人。

         现在来建图,如果A和B是同类,则从A向B连一条权值为1的无向边,否则连权值为0的,这样我们确定一个起点就能够推出整个连通块的好人坏人数量,有两种可能:(a,b),(b,a).

         但是有多个连通块,每个连通块有两种可能,我们要怎么合并答案呢?这个其实非常像背包啊,每个可以取第一个或者取第二个,我们考虑好人的数量,设f[i][j]表示前i个连通块中,有j个好人的方案数,因为有两种组合,可以得到f[i][j] = f[i-1][j - a[i]] + f[i-1][j - b[i]],看最后f[n][p1]是不是等于1就可以了。

         因为只用涉及到01运算,所以可以用bool数组优化计算.

    例8:

    分析:这道题题的题意就是如果u和v只用1条边就连通,那么|xu-xv|就必须≤1,否则就必须≥1

         看上去似乎非常难以分析得到答案,我们从最简单的情况来分析:

         1.两个点,这个直接弄就好了。

         2.三个点,这个有很多方法,比如112,221什么的。

         3.四个点:

    会发现无论怎么样都不能满足要求,但是如果我们在中间加上一笔:

    可以发现最左边和最右边填1,上面填2,下面填0就能满足要求,现在我们思考一下,为什么左边和右边的能填成一样的?可以发现,这两个点所连上的点加上自己的集合相等,而上下两个不相等.为什么会这样呢?因为这个集合里保存的都是直接与自己相连的,都满足限制|xu-xv|≤1,如果两个集合不相等,那么就有点不在某个集合内,这个数值就必须要改变。

       根据这个性质,我们可以把两个填相同数值的点合并成一个点,可能会出现这样的情况:

    然后我们可以发现,下面的点还可以继续合并,这就与之前的合并矛盾了,所以如果图中存在度数大于等于3的点,那么一定是无解的.

       那么接下来就只有两种可能了:环或者链,合并后环是不可能出现的,因为可以继续合并成1个点,那么只有链了,从头到尾依次填0,1,2,3,...,n即可,不过注意我们操作的是合并后的点,记得一定要还原.

    例9:

    分析:这道题是经典的A*算法的题,A*算法其实和IDA*算法差不多,只不过用优先队列维护,每次估价当前状态到目标状态的可能的值,一般是估优不估劣,如果当前已经用的代价加上估计的代价超过了已有的答案,就不走,这是IDA*,对于A*,我们每次从优先队列中取出已用代价+估计代价的最小的元素来扩展,这里的估价代价就是当前点到终点的最短路,可以先用一次spfa来求,可以知道,这个估价函数是一定估优不估劣的,所以可以用A*算法.

         对于怎么求K短路,如果终点被入队了k次,那么求出来的就是k短路。

         总体思路就是先倒着做一次spfa,求终点到每个点的最短路来得到估价函数,然后用A*算法来求第k短路。(需要注意的一点是判负环!)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    #include <queue>
    
    using namespace std;
    
    int n, m, head[200010], to[200010], nextt[200010], tot, head2[200010], to2[200010], nextt2[200010], tot2, w[200010], w2[200010];
    int vis[200010], d[200010],s,t,k,ans;
    bool flag = true;
    
    struct node
    {
        int x, dist, f;
        bool operator < (const node &a) const {
            if (a.f == f)
                return a.dist < dist;
            return a.f < f;
        }
    };
    
    void add1(int x, int y, int c)
    {
        to[tot] = y;
        w[tot] = c;
        nextt[tot] = head[x];
        head[x] = tot++;
    }
    
    void add2(int x, int y, int c)
    {
        to2[tot2] = y;
        w2[tot2] = c;
        nextt2[tot2] = head2[x];
        head2[x] = tot2++;
    }
    
    void spfa()
    {
        for (int i = 1; i <= n; i++)
            d[t] = 1000000000;
        queue <int> q;
        q.push(t);
        vis[t] = 1;
        d[t] = 0;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            vis[u]--;
            if (vis[u] > n)
            {
                flag = false;
                return;
            }
            for (int i = head2[u]; i + 1; i = nextt2[i])
            {
                int v = to2[i];
                if (d[v] > d[u] + w[i])
                {
                    d[v] = d[u] + w[i];
                    if (!vis[v])
                    {
                        vis[v]++;
                        q.push(v);
                    }
                }
            }
        }
    }
    
    void A_star()
    {
        priority_queue <node> q;
        int cnt = 0;
        if (s == t)
            k++;
        node tmp;
        tmp.x = s;
        tmp.dist = 0;
        tmp.f = d[s];
        q.push(tmp);
        while (!q.empty())
        {
            node u = q.top();
            q.pop();
            if (u.x == t)
                cnt++;
            if (cnt == k)
            {
                ans = u.dist;
                return;
            }
            for (int i = head[u.x]; i + 1; i = nextt[i])
            {
                int v = to[i];
                node temp;
                temp.x = v;
                temp.dist = w[i] + u.dist;
                temp.f = temp.dist + d[v];
                q.push(temp);
            }
        }
    }
    
    int main()
    {
        memset(head, -1, sizeof(head));
        memset(head2, -1, sizeof(head2));
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add1(a, b, c);
            add2(b, a, c);
        }
        scanf("%d%d%d", &s, &t, &k);
        spfa();
        if (!flag)
            printf("-1
    ");
        else
        {
            A_star();
            printf("%d
    ", ans);
        }
        
        //while (1);
        return 0;
    }

    总结:这几道题让我学到了很多东西:
                      1.在某些情况下,并查集可以代替链表,主要节省的是查找表头的时间

                      2.存图存不下可以试试hash,对于数字hash在不超时的情况下最好用map或set,万不得已可以转化为字符串求字符串hash,麻烦的是要解决hash碰撞。

                      3.一些题如果可能很少,我们可以试试推一下每种可能的结论,例如例7

                      4.从全局分析不出来的题可以试试小范围,并逐步推广到大范围:一般是有一个递推关系或者是数学公式,否则就是不成立.

                      5.学习了A*算法和怎么求第k短路.推荐一篇博客:传送门

  • 相关阅读:
    hdu3709(数位dp)
    2012天津E题
    2012天津C题
    hdu1754(splay)
    赤裸裸的splay平衡树
    hdu(预处理+线段树)
    uva11922(强行用rope替代spaly)
    lightoj 1370 欧拉函数
    poj3294 出现次数大于n/2 的公共子串
    poj2774 后缀数组2个字符串的最长公共子串
  • 原文地址:https://www.cnblogs.com/zbtrs/p/7348090.html
Copyright © 2020-2023  润新知