zoukankan      html  css  js  c++  java
  • 一些简单二分题,简单的hash,H(i),字符串题

    说在前面: 题是乱七八糟的.  

    几个二分的题. (但是我的做法不一定是二分,有些裸暴力.

    1. Equations HDU - 1496 

    输入a,b,c,d问你这个方程有多少解.
    a*x1^2+b*x2^2+c*x3^2+d*x4^2=0
    a,b,c,d属于[-50, 50]
    x1,x2,x3,x4属于[-100,100]且Xi不等于零

    解法很简单, 把式子拆两边,拆成a*x1^2 + b*x2^2 = -(c*x3^2 + d*x4^2)的形式,然后暴力枚举就行了.  

    // 注意 如果只枚举正数开始枚举的 答案注意乘以16.

    #include <cstdio>
    #include <cstring>
    
    const int maxn = 1000010;
    
    int z[maxn], f[maxn];
    
    int main()
    {
        int x1, x2, x3, x4, tmp;
        int a, b, c, d, res;
        while (~scanf("%d%d%d%d", &a, &b, &c, &d)) {
            res = 0;
            if ((a>0 && b>0 && c>0 && d>0) || (a<0 && b<0 && c<0 && d<0))     goto A;  // 加上无解的判断 会快很多.
            memset(z, 0, sizeof(z));
            memset(f, 0, sizeof(f));
            for (x1=1; x1<=100; ++x1) 
                for (x2=1; x2<=100; ++x2) {
                    tmp = a * x1 * x1 + b * x2 * x2;
                    if (tmp < 0) f[-tmp]++;
                    else z[tmp]++; 
                }
            for (x3=1; x3<=100; ++x3) 
                for (x4=1; x4<=100; ++x4) {
                    tmp = c * x3 * x3 + d * x4 * x4;
                    if (tmp <= 0) res += z[-tmp];
                    else res += f[tmp]; 
                }    
            res *= 16;  // 注意 答案要乘以16
            A:
                printf("%d
    ", res);
        }
        
        return 0;
    }
    View Code

    2. B - Eqs  POJ - 1840

    a1x1^3+a2x2^3+a3x3^3+a4x4^3+a5x5^3=0

    和上一个题一样的问方程有多少解,我们拆开.

    然后注意到 a1x1^3+a2x2^3 = -(a3x3^3+a4x4^3+a5x5^3) 左边只有2500种值,

    所以我们不妨把值存起来,然后二分就可以简单过了.

    二分思路很好想,但是我....做的时候SB了,没有用二分写.

    还是直接暴力,但是爆内存了.然后我就类似前向星一样建了个hash表来存值.然后继续暴力...

    #include <cstdio>
    #include <cstring>
    #include <map> 
    
    using namespace std;
    
    int qsz;
    
    int head[13500000];
    int hash_table[1000000];
    int hash_val[1000000];
    
    void add(int val) {
        hash_val[qsz] = val;
        hash_table[qsz] = head[abs(val)];
        head[abs(val)] = qsz++;    
    }
    
    int find(int val) {
        int res = 0;
        for (int i=head[abs(val)]; i; i=hash_table[i]) {
            if (val + hash_val[i] == 0) res++;
        //    printf("%d ", i);
        }
            
        
        return res;
    }
    
    int main()
    {
        int a, b, c, d, e;
        while (scanf("%d%d%d%d%d", &a, &b, &c, &d, &e)!=-1) {
            memset(head, 0, sizeof(head));
            qsz = 1;
            int x1, x2, x3, x4, x5, tmp;
            for (x1=-50; x1<=50; ++x1) {
                if (!x1) continue;
                for (x2=-50; x2<=50; ++x2) {
                    if (!x2) continue;
                    tmp = x1 * x1 * x1 * a + x2 * x2 * x2 * b;
                    add(tmp);
                }
            }
            long long res = 0;    
            for (x3=-50; x3<=50; ++x3) {
                if (!x3) continue;
                for (x4=-50; x4<=50; ++x4)  {
                    if (!x4) continue;
                    for (x5=-50; x5<=50; ++x5) {
                        if (!x5) continue;
                        tmp = x3 *x3 * x3 * c + x4 * x4 * x4 * d + x5 * x5 * x5 * e;
                        if (tmp>12500000 || tmp<-12500000) continue;
                        res += find(tmp);
                    }
                }
            }
            printf("%lld
    ", res);
        }
        return 0;
    }
    View Code

    当然,这道题还有一些有趣的事情, 如果你直接用map[value]来暴力的话,会T, 但是如果你先用map.find(value)来判断一下子的话..可以水过去.

    我发现STL用lower_bound 之类的 用类的方法 会比 函数快得多!!  

    #include <cstdio>
    #include <map> 
    
    using namespace std;
    map<int, int> z;
    
    int main()
    {
        int a, b, c, d, e;
        while (scanf("%d%d%d%d%d", &a, &b, &c, &d, &e)!=-1) {
            z.clear();
            int x1, x2, x3, x4, x5, tmp;
            for (x1=-50; x1<=50; ++x1) {
                if (!x1) continue;
                for (x2=-50; x2<=50; ++x2) {
                    if (!x2) continue;
                    tmp = x1 * x1 * x1 * a + x2 * x2 * x2 * b;
                    z[tmp]++;
                }
            }
            long long res = 0;    
            for (x3=-50; x3<=50; ++x3) {
                if (!x3) continue;
                for (x4=-50; x4<=50; ++x4)  {
                    if (!x4) continue;
                    for (x5=-50; x5<=50; ++x5) {
                        if (!x5) continue;
                        tmp = x3 *x3 * x3 * c + x4 * x4 * x4 * d + x5 * x5 * x5 * e;
                        if (z.find(tmp) != z.end()) res += z[tmp];
                    }
                }
            }
            printf("%lld
    ", res);
        }
        return 0;
    }
    View Code

    3. Sumsets POJ - 2549

    题意很简单,给你一些数,然后要求出满足a+b+c=d式子,d的最大指.  同时a,b,c,d都不相同.

    范围是1~1000个数,  我们还是不难想到拆两边,然后就有 a+b = d - c 然后我们把左边值保存起来,

    同时记录下标,然后我们再枚举右边,在左边的值中二分查找,同时核对小标. 不难发现,这是可行的..

    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    struct nobe {
        int a, b;
        ll val;
        bool operator < (const nobe &a) const {
            return val < a.val;
        }
        nobe () {}
        nobe (int ja, int jb, ll jval) : a(ja), b(jb), val(jval) {};
    };
    
    ll dt[1000500];
    nobe date[1000500];
    
    int main()
    { 
        int n, i, j;
    //    freopen("E:\input.txt", "r", stdin);
        while (scanf("%d", &n) && n) {
            for (i=1; i<=n; ++i) scanf("%lld", dt+i);
            sort(dt+1, dt+1+n);
            int cnt = 1;
            for (i=1; i<=n; ++i) 
                for (j=i+1; j<=n; ++j) {
                    date[cnt].a = i;
                    date[cnt].b = j;
                    date[cnt++].val = dt[i] + dt[j];
                //    printf("-- %I64d 
    ", date[cnt-1].val);
                }
            sort(date+1, date+1+cnt);
        //    for (i=1; i<cnt; ++i)
            int ans = 0;
            for (i=n; i>=1; --i) {
            //    printf("%I64d 
    ", dt[i]);
                for (j=1; j<=n; ++j) {
                    if (i == j) continue;
                    ll val = dt[i] - dt[j];
                //    printf("%I64d 
    ", val);
                    int left = 1, right = cnt;
                    while (left <= right) {
                        int m = (left + right) / 2;
                        if (date[m].val >= val) 
                            right = m - 1;
                        else
                            left = m + 1;
                    }
                    if (date[left].val == val) {
                //    printf("%I64d %I64d 
    ", ); 
                        if (date[left].a != i && date[left].b != i && date[left].a != j && date[left].b != j) {
                            ans = i;
                            goto A;
                        }
                    }
                    
                }
            }
            A:
            if (!ans) printf("no solution
    ");
            else printf("%lld
    ", dt[ans]);
        }
    
        return 0;
    } 
    View Code

    4. Stammering Aliens UVALive - 4513 

    一个简单字符串的题目,题意是这样的.

    给你两个数n,m和一个字符串s, 其中m表示字符的种类

    输出长度为n的子串有多少种

    范围: 子串所形成的字符集不会超过一千六百万 

    例如:   n= 3 m=4 字符串为: daababac 

    输出:   5

    解释: 长度为3的子串有五种:
    “daa”; “aab”; “aba”; “bab”; “bac“

    题目说了 子串所形成的字符集的种类不超过一千六百万,也就是说长度为n的子串最多只有一千六百万种,所以我们不妨把长度为n的子串都转化成十进制数 ,然后判种类. (这种转化成对应进制数的方法有一种对应的算法叫做: Rabin-Karp算法. 可以自己看看,不过感觉也没啥用..至少我用不到= =.)

    #include <cstdio>
    #include <cstring>
    
    using namespace std;
    
    typedef long long ll;
    
    char str[1000010];
    int  xhash[256];
    bool _hash[16000010];
    
    int main()
    {
        int n, m, i, len, cnt, pwr;
    
        while (~scanf("%d%d", &n, &m)) {
            scanf("%s", str + 1);
            len = strlen(str + 1);
            cnt = 0;
            memset(xhash, -1, sizeof(xhash));
            memset(_hash, 0, sizeof(_hash));
            for (i=1; i<=len; ++i) if(xhash[str[i]] == -1) xhash[str[i]] = cnt++;
            pwr = 1;
            for (i=1; i<n; ++i) pwr *= m;
            int res = 0;
            for (i=1; i<n; ++i) 
                res = res * m + xhash[str[i]];
            for (i=n; i<=len; ++i) {
                res = res * m + xhash[str[i]];
                _hash[res] = true;
                res = res - xhash[str[i-n+1]] * pwr;
            }
            int ans = 0;
            for (i=0; i<16000010; ++i) 
                if (_hash[i]) ans++;
            printf("%d
    ", ans);
            
        }
        
        return 0;
    }
    View Code

    这道题还有一个类似的做法,就是求出长度为n的子串的hash,然后用set保存,最好set的大小就是答案了.

    根据白书上的P224页给的hash方法,我们可以知道,预处理出H(i)以后,我们就可以O(1)的求出一段区间的hash值了.. (具体的方法介绍下面,因为懒,我直接放公式,没有好的解说,只适合自己看.所以不要看...不懂去看别的博客,公式只是加字数罢了.或者以后复习过一下大概而已.)

    #include <cstdio>
    #include <cstring>
    #include <set>
    
    using namespace std;
    
    typedef unsigned long long ull;
    
    const int maxn = 1e6+500;
    const ull SmallPrime = 31;
    char s[maxn];
    set<ull> se;
    ull pre[maxn];
    ull _hash[maxn];
    
    int main()
    {
        
        pre[0] = 1;
        for (int i=1; i<maxn; ++i) pre[i] = pre[i-1] * SmallPrime;
        
        int t, n, i, m;
        while (~scanf("%d%d", &n, &m)) {
            scanf("%s", s + 1);
            int len = strlen(s+1);
            
            se.clear();
            _hash[len+1] = 0;
            
            for (i=len; i>=1; --i) _hash[i] = _hash[i+1] * SmallPrime + s[i];
            for (i=n; i<=len; ++i) se.insert(_hash[i-n+1] - _hash[i+1] * pre[n]);
            printf("%d
    ", se.size());
        }
        
        return 0;
    }
    View Code

    -----------------------------------------------------------------------------------------------------------------------------------

    二,白书224给出了一个基于哈希值的LCP算法:
    为每个后缀计算一个哈希值 H(i) = H(i+1)*x + s[i]
    // 注意,这儿s[]下标是从1开始的.白书是从0开始的.
    所以对于
    H(i) = s[i]*x + s[i+1]*x^2 + … + s[n]*x^(n-i+1)
    所以我们对应一段子串s[l...r]它两端对应的hash值是:
    H(l) = s[l]*x + s[l+1]*x^2 + … + s[r]*x^(r-l+1) +
    + s[r+1]^(r-l+2) + … + s[n]*x^(n-l+1)
    H(r) = s[r]*x + s[r+1]*x^2 + … + s[r]*x^(n-r+1)
    所以可以定义H(l, r) = H(l) – H(r+1) * x ^ (r – l + 1)
    所以 H(l, r) = s[l]*x + x[l]*x^2 + … + x[r]*x^(r-l+1)
    // 说白了…就是类似前缀和的思想.

    也可以看图:

    当我们预处理好H(i)后,我们就可以通过减法来O(1)的求出H(l, r) 这一段的hash值了. x是一个质数. 相差x^(r-l+1)次方 

    然后预处理H(i)可以正着,也可以倒着,怎么顺手怎么写呗.一般没有什么大问题.

    -----------------------------------------------------------------------------------------------------------------------------------------------

    OK 讲到白书的哈希方法,那就把白书的题放上来.

    5.Stammering Aliens UVALive - 4513

    给一个字符串,找出至少出现m次的最长字符串.存在则输出长度及其起始位置.
    如果不存在,则输出”none”
    M<=4e4

    这个题呢,根据白书的说法可以后缀数组,也可以二分答案+hash做.

    后缀数组我不会.所以我是二分答案做的. 二分答案很暴力..

    怎么二分呢? 没错,我们就直接二分最长的字符串为mid, 然后暴力把长度为mid的所有子串的hash值求出. (这个够暴力把!)

    然后暴力判断是否有出现过m次的,如果有那么答案应该向大的方向继续二分,如果没有就往向的方向进行二分.

    (注: 为什么可以这样二分呢? 想一想,.

           如果我们二分长度为mid的子串出现了至少m次,同时我们也知道这些子串也是某串的子串,所有不难知道,如果最终答案ans一定是>=ans的.

          满足我们二分的性质,所以可以二分.)

    (再注: 有些二分的写法是需要特判1的情况的, 有些不需要.我的是需要的,白书的是不需要的.)

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    typedef unsigned long long ull;
    
    const int maxn = 1e5+10;
    const int o_o = 31;
    ull x[maxn];
    ull suf[maxn];
    ull hx[maxn];
    int rk[maxn];
    char s[maxn];
    int pos;
    
    bool cmp(int a, int b) {
        return hx[a]!=hx[b] ? hx[a]<hx[b] : a<b;
    }
    
    bool isok(int L, int n, int len) {
        int i;
        for (i=1; i<=len-L+1; ++i) {
            hx[i] = suf[i] - suf[i+L] * x[L];
            rk[i] = i;
    //        printf("%d %d %llu %llu %llu %llu %llu
    ", i, L, hx[i], suf[i] , suf[i+L] , x[len], suf[i+L] * x[len]);
        }
        sort(rk+1, rk+len-L+2, cmp);
        int tpos = 0;
        int cnt = 1;
        for (i=2; i<=len-L+1; ++i) {
            if (hx[rk[i]]!=hx[rk[i-1]]) cnt = 1;
            else cnt++;
            if (cnt >= n) {
                tpos = max(tpos, rk[i]);
            }
        //    printf("------- %d %d 
    ", rk[i-1], rk[i]);
        }
        if (tpos) pos = tpos;
        return tpos;
    }
    
    int main()
    {
    //    freopen("E:\input.txt", "r", stdin);
        int n, i, len;
        int left, right;
        x[0] = 1;
        for (i=1; i<maxn; ++i) x[i] = x[i-1] * o_o;
        
        while (~scanf("%d", &n) && n) {
            scanf("%s", s+1);
            len = strlen(s+1);
            if (n > len) {
                printf("none
    ");
                continue;
            } else if (n == 1) {
                printf("%d 0
    ", len);
                continue;
            }
            suf[len+1] = 0;
            for (i=len; i>=1; --i) suf[i] = suf[i+1] * o_o + s[i];
            int ans = 1;
            if (!isok(1, n, len)) {
                printf("none
    ");
                continue;
            }
            left = 1; right = len + 1;
            while (left <= right) {        
                int m = (left + right) >> 1;
                if (!isok(m, n, len)) {
                     right = m - 1;
                } else{
                    ans = m;
                    left = m + 1;
                }
            }
            isok(ans, n, len);
            
            printf("%d %d
    ", ans, pos-1);
        }
        
        
        return 0;
    }
    View Code

    OK,然后继续.

    刚才的题是不带修改的,那么如果修改的呢?

    6. AC's String HDU - 3973

    给你一个单词的集合W和一个字符串S.有2个操作, 

    1,修改, 改变字符串的某个字符.
    2,询问, 询问某子串是否属于为单词集合.属于输出Yes, 不属于输出No
    范围: 多Case. T<=20 单词数 <= 1e4 单词字符总数 <= 2e6 字符串长度|S| <= 1e5 操作 <= 1e5
    如果不带修改,那么很明显的,直接预处理出hash函数然后就可以O(1)的查询了, 但是带了修改. 那我们可不可以通过维护H(i)来支持修改,然后继续快速的查询呢? 怎么维护呢?

    答案是线段树. 详细见图片.

    这是建树的时候


    这是我们建好的树

    然后这是查询的时候

    假设我们查询[3,5]这个子串

    OK 然后这是代码.. (为什么没有解说呢....因为..我觉得你看着图 或者 自己画个图,只有会写线段树和理解哈希的方法H(i),就很简单了....好吧是我说不来.靠图来凑.2333

    #include <cstdio>
    #include <cstring>
    #include <set>
    
    using namespace std;
    
    typedef unsigned long long ull;
    #define lson rt<<1
    #define rson rt<<1|1
    
    const int maxn = 2e6+500;
    const ull SmallPrime = 31;
    char s[maxn];
    set<ull> se;
    ull pre[maxn];
    
    ull _hash()
    {
        int i, len = strlen(s + 1);
        ull res = 0;
        for (int i=1; i<=len; ++i) 
            res = res + (s[i] - 'a' + 1) * pre[i];
        return res;
    }
    
    struct nobe {
        int l;
        int r;
        ull val;
    }te[maxn<<2];
    
    inline void pshup(int rt)
    {
        te[rt].val = te[lson].val + te[rson].val * pre[te[lson].r - te[lson].l + 1];
    }
    
    void build(int l, int r, int rt)
    {
        te[rt].l = l; te[rt].r = r;
        if (l == r) {
    //        te[rt].val = s[l] * pre[1];
            te[rt].val = (s[l] - 'a' + 1) * pre[1];
    //        te[rt].val = (s[l] - 'a' + 1);
            return ;
        }
        int m = (l + r) >> 1;
        build(l, m, lson);
        build(m+1, r, rson);
        pshup(rt);
    }
    
    void update(int l, int rt)
    {
        if (l==te[rt].l && te[rt].r==l) {
            te[rt].val = (s[l] - 'a' + 1) * pre[1];
    //        te[rt].val = (s[l] - 'a' + 1) ;
            return ;
        }
        int m = (te[rt].l + te[rt].r) >> 1;
        if (l <= m)  update(l, lson);
        if (l  > m)  update(l, rson);
        pshup(rt);
    }
    
    ull query(int l, int r, int rt)
    {
        if (l>te[rt].r || te[rt].l>r) return 0;
        if (l<=te[rt].l && r>=te[rt].r) 
            return te[rt].val;
            
        int m = (te[rt].l + te[rt].r) >> 1;
        if (r <= m) return query(l, r, lson);
        else if (l > m) return query(l, r, rson);
        else return query(l, m, lson) + query(m+1, r, rson) * pre[m - l + 1];  // 注意这里哟. 这也是图为什么一步一步画,我想体现的就是这一步..当然,能不能传达给你们,不管我的事2333
    }
    
    int main()
    {
        pre[0] = 1;
        for (int i=1; i<maxn; ++i) pre[i] = pre[i-1] * SmallPrime;
        
        int t, n, i;
        scanf("%d", &t);
        for (int cas=1; cas<=t; cas++) {
            se.clear();
            scanf("%d", &n);
            for (i=1; i<=n; ++i) {
                scanf("%s", s+1);
                se.insert(_hash());
            }
            scanf("%s", s+1);
            n = strlen(s+1);
            build(1, n, 1);
            int q;
            printf("Case #%d:
    ", cas);
            scanf("%d", &q);
            char op[16];
            int l, r;
            for (i=1; i<=q; ++i) {
                scanf("%s", op);
                if (op[0] == 'C') {
                    scanf("%d%s", &l, op);
                    ++l;
                    s[l] = op[0];
                    update(l, 1);
                } else {
                    scanf("%d%d", &l, &r);
                    ++l; ++r;
                    printf("%s
    ", (se.find(query(l, r, 1)) != se.end()) ? "Yes" : "No");
                }
            }
        }
        
        return 0;
    }
    View Code

    emmmmm  不想写了.  

  • 相关阅读:
    form 元素横向排列
    mysql5.6 主从同步
    centos iptables
    angular 数据绑定
    angular路由 模块 依赖注入
    angular $scope对象
    angular前端开发环境
    使用STM32CubeMX生成RTC工程[闹钟中断2]
    利用STM32CubeMX生成HID双向通讯工程
    使用STM32CubeMX生成RTC工程[秒中断]
  • 原文地址:https://www.cnblogs.com/cgjh/p/9511629.html
Copyright © 2011-2022 走看看