一些简单二分题,简单的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\n", 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\n", 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\n", 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 \n", 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 \n", dt[i]);
            for (j=1; j<=n; ++j) {
                if (i == j) continue;
                ll val = dt[i] - dt[j];
            //    printf("%I64d \n", 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 \n", ); 
                    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\n");
        else printf("%lld\n", 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\n", 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\n", 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\n", 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 \n", 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\n");
            continue;
        } else if (n == 1) {
            printf("%d 0\n", 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\n");
            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\n", 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:\n", 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\n", (se.find(query(l, r, 1)) != se.end()) ? "Yes" : "No");
            }
        }
    }
    
    return 0;
}
View Code

 

emmmmm  不想写了.  

posted @ 2018-08-21 15:31  过路人1998  阅读(551)  评论(0编辑  收藏  举报