·专题」 Hash

总算到了写Hash这个专题的时候,以前一直都搞不清Hash到底有什么用,

现在才发现Hash在各种领域包括ACM的重要性,判重是灰常灰常重要的。

 

下面应该会总结

整数的hash(线性的, 康托展开)

字符串Hash( ELFHash, 多项式Hash, BKRDHash)

Hash的灵活应用



Hash函数运用很广,自己见识浅薄,目前来看最大的用途就是判重了。

 

自己简单总结一下:(其实我是参考黑书的)

哈希表的思路是根据关键码值直接访问。也就是说,这是一个把关键码值映射到表中的一个位置来访问记录的过程。
这个映射函数叫做hash函数,用h来表示,存放记录的数组叫做哈哈希表。表里的每一个位置叫做一个槽,槽的数目用M表示。 哈希表的插入和查找算法 (
1)计算函数值h(K) (2)从槽h(K)开始,使用(如果需要的话)冲突解决策略定位包括关键码K的记录 (3)如果需要插入,在槽里把数据插入即可。 冲突解决办法 开散列(拉链法) 闭散列(开放寻址法)

 

1. HDU 1425 sort

题意:给定一个序列,从大到小输出前m大的数,可以用sort排序之后水过。
分析:这里是我学习的第一个Hash思想,

就是最简单的完全散列,将数组下标和key对应起来
#include <cstdio>

#include <cstring>

#include <algorithm>



using namespace std;

#define M 1000005



bool Hash[M];

int m,n,a;



int main()

{

    while(~scanf("%d%d",&n,&m))

    {

        for(int i=0;i<n;i++)

        {

            scanf("%d",&a);

            Hash[a+500000] = true;

        }

        

        for(int i=1000000;m>0;i--)

        {    

            if(Hash[i])

            {

                if(m!=1)

                    printf("%d ",i-500000);

                else

                    printf("%d\n",i-500000);

                m--;

            }

        }

    }

    return 0;

}
代码君
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;



int x[1000000+10];

int n,k;



void quicksort(int b,int e,int a[])

{

    int i = b, j = e, x = a[(b+e)>>1];

    do

    {

        while(a[i] > x)    i++;

        while(a[j] < x) j--;

        if(i <= j)    swap(a[i++],a[j--]);

    }while(i < j);

    if(j < e)    quicksort(i,e,a);

    if(j > b)    quicksort(b,j,a);

}



int main()

{

    while(~scanf("%d%d",&n,&k))

    {

        for(int i=0;i<n;i++)

            scanf("%d",&x[i]);

        quicksort(0,n-1,x);

        for(int i=0;i<k-1;i++)

            printf("%d ",x[i]);

        printf("%d\n",x[k-1]);

    }

    

    return 0;

}
存一下快排

 

2. HDU 1800 Flying to Mars

题意:N个soldiers,每个soldier有一个等级,高等级的soldier可以教低等级的soldier,反之不能,教学需要用一个broomsticks,
   等级依次递减的soldiers可以共用一个broomstick,问最少需要多少个。
分析:很容易想到是出现次数最高的等级,然后直接输出出现次数。
题解:题目数据很弱,但是正规的作法就是要用字符串Hash,还需要考虑前置0的问题
   
字符串Hash,这里我学习了 (累乘 seed)(ELFHash函数) 两种
int HashFunc()

{

    int Hash = 0;

    int i = 0;

    while(s[i] == '0') i++;

    for(;s[i]!='\0';i++)

    {

        Hash += ((Hash * seed[s[i]&1] + s[i]) % MOD);

    }

    return Hash;

}
累乘seed
unsigned int ELFHash(char* str)

{

    unsigned int Hash = 0;

    unsigned int x = 0;



    while(*str)

    {

        Hash = (Hash << 4)  + (*str++);    //hash左移4位,把当前字符ascii存入hash第四位

        if((x == Hash & 0xF0000000L) != 0 )

        {//如果最高位的思位不为0,则说明字符多余7个,现在正在存第8个字符,如果不处理,再加入下一个字符时,第一个字符会被移出。

            Hash ^= (x >> 24);//因为1-4位刚刚存储了新加入到字符,所以不能>>28 

            Hash &= ~x; //上面这行代码并不会对X有影响,本身X和hash的高4位相同,下面这行代码&~即对28-31(高4位)位清零。  

        }

    }

//返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负)

    return (Hash & 0x7FFFFFFF)
#include <cstdio>

#include <cstring>

#include <algorithm>

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 1000007

using namespace std;



int n,HashTable[M];

char str[33];

unsigned int ELFHash(char* str)

{

    unsigned int Hash = 0;

    unsigned int x = 0;



    while(*str)

    {

        Hash = (Hash << 4)  + (*str++);

        if((x == Hash & 0xF0000000L) != 0 )

        {

            Hash ^= (x >> 24);

            Hash &= ~x;

        }

    }



    return (Hash & 0x7FFFFFFF);

}



int solve(char* str)

{

    CLR(HashTable,0);

    int ret = 0;

    for(int i=0;i<n;i++)

    {

        scanf("%s",str);

        while(*str == '0')   str++;

        int Hash = ELFHash(str)%M;

        HashTable[Hash]++;

        ret = max(HashTable[Hash],ret);

    }

    return ret;

}



int main()

{

    while(~scanf("%d",&n))

    {

        printf("%d\n",solve(str));

    }

    return 0;

}
代码君一
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;



#define MOD 10000007

#define MAXN 3003

#define LEN 31

const int seed[2] = {13,37};

int level[MAXN], n ;

char s[LEN];



int HashFunc()

{

    int Hash = 0;

    int i = 0;

    while(s[i] == '0') i++;

    for(;s[i]!='\0';i++)

    {

        Hash += ((Hash * seed[s[i]&1] + s[i]) % MOD);

    }

    return Hash;

}



int main()

{

    while(~scanf("%d",&n))

    {

        for(int i=0;i<n;i++)

        {

            scanf("%s",s);

            level[i] = HashFunc();

        }

        sort(level,level+n);

        int res = 0;

        for(int i=0;i<n;)

        {

            int j = i+1;

            while(j<n && level[j] == level[i])    j++;

            if(res < j - i)

                res = j - i;

            i = j;

        }    

        printf("%d\n",res);

    }

    

    

    return 0;

}
代码君二,这样处理会更快一些,300ms左右,有点像原来那个sticks贪心
// ELF Hash Function

unsigned int ELFHash(char *str)

{

    unsigned int hash = 0;

    unsigned int x = 0;



    while (*str)

    {

        hash = (hash << 4) + (*str++);//hash左移4位,把当前字符ASCII存入hash低四位。 

        if ((x = hash & 0xF0000000L) != 0)

        {

            //如果最高的四位不为0,则说明字符多余7个,现在正在存第8个字符,如果不处理,再加下一个字符时,第一个字符会被移出,因此要有如下处理。

            //该处理,如果对于字符串(a-z 或者A-Z)就会仅仅影响5-8位,否则会影响5-31位,因为C语言使用的算数移位

            //因为1-4位刚刚存储了新加入到字符,所以不能>>28

            hash ^= (x >> 24);

            //上面这行代码并不会对X有影响,本身X和hash的高4位相同,下面这行代码&~即对28-31(高4位)位清零。

            hash &= ~x;

        }

    }

    //返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负)

    return (hash & 0x7FFFFFFF);

}
ELFHash
#include <cstdio>

#include <cstring>

#include <algorithm>

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 1000007

using namespace std;



int n,HashTable[M];

char str[33];

unsigned int ELFHash(char* str)

{

    unsigned int Hash = 0;

    unsigned int x = 0;



    while(*str)

    {

        Hash = (Hash << 4)  + (*str++);

        if((x = Hash & 0xF0000000L) != 0 )

        {

            Hash ^= (x >> 24);

            Hash &= ~x;

        }

    }



    return (Hash & 0x7FFFFFFF);

}



int solve(char* str)

{

    CLR(HashTable,0);

    int ret = 0;

    for(int i=0;i<n;i++)

    {

        scanf("%s",str);

        while(*str == '0')   str++;

        int Hash = ELFHash(str)%M;

        HashTable[Hash]++;

        ret = max(HashTable[Hash],ret);

    }

    return ret;

}



int main()

{

    while(~scanf("%d",&n))

    {

        printf("%d\n",solve(str));

    }

    return 0;

}
代码君三

 

 

 

3. HDU 1496 Equations

题意:给定a,b,c,d四个数,问使得 式子 a*x1^2+b*x2^2+c*x3^2+d*x4^2=0 成立的条件x1,x2,x3,x4 的组合有多少种。
   题目限定( x1,x2,x3,x4 ) that verifies the equation, xi is an integer from [-100,100]
分析:这题也是用到的Hash的思想,我们可以这样来做,把式子分为两边,将一部分移到右边去
枚举左边式子,存入Hash表当中,然后再枚举右边的式子,看是否和左边相等,如果是的话,res++
   因为这里的xi 是 [-100,100], 所以有正负两种情况,2*2*2*2 = 16,最后记得*16。还有就是要一个优化,判断是a,b,c,d是否同号

这个枚举属于完全散列,没有冲突,数组开200w可以接受(枚举+完全散列)
#include <cstdio>

#include <cstring>

#define CLR(a,b) memset(a,b,sizeof(a))

int Hash[2000020];

int a,b,c,d,cnt;



int main()

{

    while(~scanf("%d %d %d %d",&a,&b,&c,&d))

    {

        if(a*b>0 &&  b*c>0 && c*d>0)

        {

            printf("0\n");

            continue;

        }

        cnt = 0;

        CLR(Hash,0);

        for(int i=1;i<=100;i++)

            for(int j=1;j<=100;j++)

                Hash[a*i*i+b*j*j+1000000]++;

                

        for(int i=1;i<=100;i++)

            for(int j=1;j<=100;j++)

                cnt += Hash[-c*i*i-d*j*j+1000000];

                

        printf("%d\n",cnt*16);

    }    

    

    return 0;

}
代码君,Hash数组要开到200W以上,具体自己看

  

 4. HDU 4334 Trouble

题意:给定5个集合,从每个集合中取出一个数分别为a1,a2,a3,a4,a5,问是否能够有a1+a2+a3+a4+a5 = 0
分析:这题和1496差不多,五个集合,分成两个 和 三个 来考虑。

这个枚举用了线性探测解决冲突问题(枚举+散列+线性探测解决冲突)
 

#include <cstdio>

#include <cstring>

#include <algorithm>



using namespace std;

typedef __int64 ll;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 300007 

ll S[5][220];

ll Hash[M];

bool flag[M];

int T,N;



int getHash(ll k)

{

    ll key = k%M;

    if(key < 0)

        key += M;

    while(flag[key] && Hash[key] != k)    //解决冲突

    {

        key = (key + 1) %M;

    }

    return key;

}



int main()

{

    scanf("%d",&T);

    while(T--)

    {

        CLR(flag,false);

        scanf("%d",&N);

        for(int i=0;i<5;i++)

            for(int j=0;j<N;j++)

                scanf("%I64d",&S[i][j]);

        for(int i=0;i<N;i++)

        for(int j=0;j<N;j++)

        {

            int key = getHash(S[0][i] + S[1][j]);

            Hash[key] = S[0][i] + S[1][j];

            flag[key] = true;

        }

        

        for(int i=0;i<N;i++)

        for(int j=0;j<N;j++)

        for(int k=0;k<N;k++)

        {

            int key = getHash(-S[2][i] - S[3][j] - S[4][k]);

            if(flag[key])

            {

                puts("Yes");

                goto AC;

            }

        }

        puts("No");    

        

        AC:;

    }

    return 0;

}    

    
代码君一,思路如上,速度还可以
http://blog.csdn.net/qq564690377/article/details/7824691
代码君二,思路很巧
 
  

 

 5. HDU 1043 Eight

题意:不需要再解释,传说中不做人生会不完整的八数码问题。
分析:八数码已经达到了10重境界。我无法分析
题解:这题我主要是学习康托展开Hash函数的。

后续:后续如果有时间或者能力的话要研究八数码的多重境界才行啊

康托展开是一种特殊的哈希函数



X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!



其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。

例如,3 5 7 4 1 2 9 6 8 展开为 98884。因为X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884.



解释:



排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为2*8!



排列的第二位是5,比5小的数有1、234,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为3*7!



以此类推,直至0*0!



用途 

显然,n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。



康托展开的逆运算

既然康托展开是一个双射,那么一定可以通过康托展开值求出原排列,即可以求出n的全排列中第x大排列。



如n=5,x=96时:



首先用96-1得到95,说明x之前有95个排列.(将此数本身减去!)

用95去除4! 得到3余23,说明有3个数比第1位小,所以第一位是4.

用23去除3! 得到3余5,说明有3个数比第2位小,所以是4,但是4已出现过,因此是5.

用5去除2!得到2余1,类似地,这一位是3.

用1去除1!得到1余0,这一位是2.

最后一位只能是1.

所以这个数是45321.



按以上方法可以得出通用的算法。
康托展开,摘自wiki
int Cantor(int* s,int len)

{

    int i, j, ret;

    for(i=0;i<len;i++)

    {

        for(j=i+1;j<len;j++)

        {

            int cnt = 0;

            if(s[i] > s[j])

                cnt++;

        }

        ret += cnt * fac[len-i-1];

    }

    return ret;

}
康托展开,.,...
#include <cstdio>

#include <cstring>

#include <algorithm>

#include <string>

#include <queue>

#include <iostream>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define MAXN 366000

//9! = 362880总共状态数 

int fac[] = {1,1,2,6,24,120,720,5040,40320,362880};

bool vis[MAXN];

string path[MAXN];

//4个方向 

int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};

//路劲需要放过来存放,原来对应的是udlr 

char cdir[] = "durl";

int ok = 0;



struct Node

{

    int s[9];

    int loc;

    int Hash;

    string path;

};



int Cantor(int* s)

{

    int ret = 0;

    for(int i=0;i<9;i++)

    {

        int cnt = 0;

        for(int j=i+1;j<9;j++)

            if(s[j] < s[i])

                cnt++;

        ret += cnt * fac[9-i-1];    //康托展开公式 

    }

    return ret; 

}



void BFS()

{

    CLR(vis,false);

    Node cur, next;

    for(int i=0;i<9;i++)

        cur.s[i] = i+1;

    cur.loc = 8;

    cur.Hash = ok;

    cur.path = "";



    

    queue<Node> que;

    que.push(cur);

    path[ok] = "";

    

    while(!que.empty())

    {

        cur = que.front();

        que.pop();

        int x = cur.loc/3;

        int y = cur.loc%3;

        for(int i=0;i<4;i++)

        {

            int xx = x + dir[i][0];

            int yy = y + dir[i][1];

            if(xx < 0 || xx > 2 || yy < 0 || yy > 2)

                continue;

            next = cur;

            next.loc = xx*3 + yy;

            next.s[cur.loc] = next.s[next.loc];

            next.s[next.loc] = 9;

            next.Hash = Cantor(next.s);

            if(!vis[next.Hash])

            {

                vis[next.Hash] = true;

                next.path = cdir[i] + next.path;

                que.push(next);

                path[next.Hash] = next.path;

            }

        }

    }

}



char str[100];

Node node;



int main()

{

    BFS(); //预处理

    while(gets(str))

    {

        for(int i=0,k=0;str[i];i++)

        {

            if(str[i] <= '9' && str[i] >= '0')

                node.s[k++] = str[i] - '0';

            else if(str[i] == 'x')

                node.s[k++] = 9;

        }

        node.Hash = Cantor(node.s);

        if(node.Hash == ok)

            puts("");

        else if(vis[node.Hash])

        {

            cout<<path[node.Hash]<<endl;

        }

        else

            puts("unsolvable");

    } 

        

    return 0;

}
代码君

 

 
  

 6. HDU 2648 Shopping

题意:超市中,每个物品每天都会涨价,对特定的一个物品,输出每天在所有商品中的价格的rank
分析:主要是字符串Hash,需要处理冲突问题,这里学习了KID大神的代码,果然神速。。我居然进第一版了。
题解:大概思路,用的字符串Hash+vector来处理的。vector不定长数组果然还是有犀利的时候。当然,这题可以用map水过,但是速度就慢很多了。

这里用的是BKRDHash函数,确实挺好用的,写起来比ELFHash好写啊。
还有这里vector巧妙避免冲突也是相当给力啊
 1 unsigned int BKDRHash(char* str)

 2 {

 3     unsigned int seed = 131;

 4     unsigned int hash = 0;

 5     while(*str)

 6     {

 7         hash = hash*seed + (*str++);

 8     }

 9     return (hash & 0x7FFFFFFF);

10 }
BKRDHash

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <vector>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 3131

struct Shop

{

    int price;

    char name[33];

}shop;



int  n,m,ans,rank;

vector<Shop> Hash[M];



unsigned int BKDRHash(char* str)

{

    unsigned int seed = 31;

    unsigned int hash = 0;

    while(*str)

    {

        hash = hash*seed + (*str++);

    }

    return (hash & 0x7FFFFFFF) % M;

}



int main()

{    

    while(~scanf("%d",&n))

    {

        for(int i=0;i<M;i++)

            Hash[i].clear();

        for(int i=0;i<n;i++)

        {

            scanf("%s",shop.name);

            shop.price = 0;

            unsigned int  t = BKDRHash(shop.name);

            Hash[t].push_back(shop);

        }

        scanf("%d",&m);

        while(m--)

        {

            for(int i=0;i<n;i++)

            {

                scanf("%d %s",&shop.price,shop.name);

                unsigned int  t = BKDRHash(shop.name);

                for(int j=0;j<Hash[t].size();j++)

                {

                    if(strcmp(shop.name,Hash[t][j].name) == 0)

                    {

                        Hash[t][j].price += shop.price;

                        if(strcmp(shop.name,"memory") == 0)

                            ans = Hash[t][j].price;

                        break;

                    }

                }

            }

            rank = 1;

            for(int i=0;i<M;i++)

                for(int j=0;j<Hash[i].size();j++)

                    if(Hash[i][j].price > ans)

                        rank++;



            printf("%d\n",rank);

        }    

    }

    return 0;

}
代码君一,相当给力,M的大小3131恰到好处,vector处理很强大
#include <iostream>

#include <string>

#include <map>

using namespace std;

string name[10001];

int n,m;

int main()

{

    while(~scanf("%d",&n))

    {

        for(int i=0;i<n;i++)

            cin>>name[i];

        scanf("%d",&m);

        map<string,int> M;

        map<string,int>:: iterator it;

        while(m--)

        {

            int price;

            string shop;

            for(int i=0;i<n;i++)

            {

                cin>>price>>shop;

                M[shop] += price;

            }

            int cnt = 0;

            for(it=M.begin();it!=M.end();it++)

                if(it->second > M["memory"])

                    cnt++;

            cout<<cnt+1<<endl;

        }    

    }

        

    return 0;

}
代码君二,map君水过

 

 
  

 7. HDU 4277 USACO ORZ

题意:给出N段线段,用所有这些线段,能够组成多少种不同的三角形。
分析:dfs深搜+hash判重。。set判重也可以。注意这里还要考虑的dfs剪枝,也是判重。就是a,b,c是组合,不是排列。
题解:速度还算可以吧。500ms

这里用的是乱七八糟的方法构造了一个hashtable。
#include <cstdio>

#include <cstring>

#include <algorithm>



using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 1000007

int s[55],n,t;

struct hashtable

{

    int size,ans[M][3],next[M],h[M];

    int hash(int a,int b,int c)

    {

        return (((((a*131)+b)*131+c)*131)&0x7fffffff)%M;

    }

    

    void insert(int a,int b,int c)

    {

        int id = hash(a,b,c);

        for(int i=h[id];i>=0;i=next[i])

            if(ans[i][0] == a && ans[i][1] == b && ans[i][2] == c)    return;

        ans[size][0] = a, ans[size][1] = b, ans[size][2] = c;

        next[size] = h[id], h[id] = size++;

    }

    

    void clear()

    {

        CLR(h,-1);

        size = 0;

    }

}g;



int ai,bi,ci;



void check(int a,int b,int c)

{

    if(a + b <= c || a + c <= b || b + c <= a)

        return;

    g.insert(a,b,c);

}



void dfs(int id)

{

    if(id >= n)

    {

        if(ai <= bi && bi <= ci)    check(ai,bi,ci);

        return;

    }

    

    ai += s[id];

    dfs(id+1);

    ai -= s[id];

    bi += s[id];

    dfs(id+1);

    bi -= s[id];

    ci += s[id];

    dfs(id+1);

    ci -= s[id];

    

}



int main()

{

    scanf("%d",&t);

    while(t--)

    {

        g.clear();

        scanf("%d",&n);

        for(int i=0;i<n;i++)

            scanf("%d",&s[i]);

        ai = bi = ci = 0;

        dfs(0);

        printf("%d\n",g.size);

    }

    return 0;

}
代码君一,,其实以后再用什么素数MOD的话就用300007,1000003好了,还有就是131,37等等
#include <cstdio>

#include <cstring>

#include <algorithm>



using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 300007

int T,N,a,b,c;

int x[16];



struct HashTable

{

    int Hash[M],ans[M][3],size;

    int gethash()

    {

        return (((((a*37)+b)*131+c)*31)&0x7fffffff) % M;

    }

    

    void insert()

    {

        int key = gethash();

        while(Hash[key])

        {

            if(ans[key][0] == a && ans[key][1] == b && ans[key][2] == c)

                return;

            key++;

        }

        Hash[key] = 1;

        ans[key][0] = a , ans[key][1] = b , ans[key][2] = c;

        size++;

    }

    void init()

    {

        size = 0;

        CLR(Hash,0);

        CLR(ans,0);

    }

}Table;



void check()

{

    if(a+b>c && a+c>b && b+c>a)

    {

        if(a >=b && b >= c)

            Table.insert();

    }

}

void DFS(int step)

{

    if(step >= N)

    {

        check();

        return;

    }

    a += x[step];  

    DFS(step+1);  

    a -= x[step];  

    b += x[step];

    DFS(step+1);  

    b -= x[step];  

    c += x[step];

    DFS(step+1);

    c -= x[step]; 

    

}

int main()

{

    scanf("%d",&T);

    while(T--)

    {

        Table.init();

        scanf("%d",&N);

        for(int i=0;i<N;i++)

            scanf("%d",&x[i]);

        a = b = c = 0;

        DFS(0);

        printf("%d\n",Table.size);

    }

    return 0;

}

    
代码君二

 

 

 8. HDU 2523 SORT AGAIN

题意:对于一个序列,任意两个数组的组合数为ABS(x[i]-x[j]),求第K大的组合数,
分析:看清楚题意,第K大是如何定义的。
题解:简单Hash直接标记之,然后AC之,这里坑爹的是ABS里面写错,导致WA N次。

完全散列
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

#define ABS(a,b) a-b>0?a-b:b-a

#define CLR(a,b) memset(a,b,sizeof(a))

int T,N,K,cnt,sta;

int key[1010],Hash[2020];



int main()

{

    scanf("%d",&T);

    while(T--)

    {

        scanf("%d %d",&N,&K);

        for(int i=0;i<N;i++)

            scanf("%d",&key[i]);

        CLR(Hash,0);

        

        for(int i=0;i<N;i++)

            for(int j=i+1;j<N;j++)

                Hash[ABS(key[i],key[j])]++;

        cnt = sta = 0;

        while(Hash[sta] == 0) sta++;

        for(;sta<=2000;sta++)

        {

            if(Hash[sta])

            {

                cnt++;

                if(cnt == K)

                {

                    printf("%d\n",sta);

                    break;

                }

            }

        }

    }

    return 0;

}
代码君

 

 

 

 9. HDU 4648 Magic Pen

题意:给定一个序列,以及一个M,问从这个序列中找出一个连续的子序列,使得子序列之和%M为0
分析:此题有陷阱,一个是分数可能还会为负数,取模的时候一定要注意处理。第二个是O(n^2)的枚举如果没有优化会超时的
具体是代码 for(int j=n; j>i && j-i>ans; j--) //&& j-i>ans这句很关键,还有就是倒过来扫。
题解:迭代求出序列的和的mod,再枚举,区间差。

还有一个思路,做法就是维护一个前序和的模,并把这个下标放到Hash表中,然后在Hash表中找一个下标距离最远的删掉即可
依然要注意的是0的初始化问题,和shopping那题有点像。使用vector
#include <cstdio>

#include <cstring>

#include <algorithm>



using namespace std;

int n,m;

int sum[100010], x[100010];



int main()

{

    while(~scanf("%d %d",&n,&m))

    {

        sum[0] = 0;

        for(int i=1;i<=n;i++)

        {

            scanf("%d",&x[i]);

            sum[i] = sum[i-1] + x[i];

            sum[i] = sum[i] % m;

            if(sum[i] < 0)

                sum[i] += m;     

        }

        

        int ans = 0;

        for(int i=0;i<=n;i++)

        {

            for(int j=n; j>i && j-i>ans; j--) //&& j-i>ans这句很关键 

            {

                if((sum[j]-sum[i])%m == 0)

                    ans = max(ans,j-i);

            }

        }

        printf("%d\n",ans);

    }

    return 0;

}
代码君一
http://blog.csdn.net/zhangyanxing666/article/details/9812335

http://blog.csdn.net/dongdongzhang_/article/details/9797905
代码君2-n,有很好的思路

 

 

 10. HDU 4020 Ads Proposal

题意:N个customs,M个ad,给个ad有对应的custom,lens和click,总共有Q次询问,每次询问输出所有customs的排名前k位的ad的len总和
  the sum of total length of the top k number of clicks for each customer.
分析:这道题做得非常不顺利,或者说是磕磕碰碰,开始用的是两个排序,第一次是一用户为关键字进行排序,第二次是在同用户中一click为关键字进行排序。
后来见到有很好的hash思想。hash[i]表示依次遍历时customer i出现的次数,也就是该用户拥有几个ad.这样的遍历是线性的。

#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define MAX 500100

int N,M,Q,U,C,L,T,cnt = 0;



int name[MAX], rank[MAX], q;

__int64 click[MAX], len[MAX], ans[MAX], res[MAX];

int Hash[100100];



int cmp(const void *_p,const void *_q)

{

    int *a=(int *)_p;

    int *b=(int *)_q;

    return click[*a]>click[*b]?-1:1;

}



/*int cmp2(int x,int y)

{

    return click[x] > click[y];

}

*/

int main()

{

    scanf("%d",&T);

    while(T--)

    {

        scanf("%d %d %d",&N,&M,&Q);

        for(int i=0;i<M;i++)

        {

            scanf("%d %I64d %I64d",&name[i],&click[i],&len[i]);

            rank[i] = i;

        }

        qsort(rank,M,sizeof(rank[0]),cmp);

        //sort(rank,rank+M,cmp2);

        for(int i=1;i<=N;i++)

            Hash[i] = 0;

        for(int i=1;i<=M;i++)

            ans[i] = 0;

        for(int i=0;i<M;i++)

        {

            Hash[name[rank[i]]]++;

            ans[Hash[name[rank[i]]]] += len[rank[i]];

        }

        res[0] = 0;

        for(int i=1;i<=M;i++)

            res[i] = ans[i] + res[i-1];

          printf("Case #%d:\n",++cnt);        

        for(int i=1;i<=Q;i++)

        {

            scanf("%d",&q);

            if(q > M)

                printf("%I64d\n",res[M]);

            else

                printf("%I64d\n",res[q]);

        }

    }

    return 0;

}

    
代码君一,突然发现qsort和sort还是有很多不同的。
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define MAX 500050



int cnt = 0;

int t,n,m,q,k,i;

__int64 ans[MAX];

struct Node

{

    __int64 u,c,l,rank;

    bool operator<(const Node t)const

    {

        if(t.u == u)

            return t.c < c;

        return t.u > u;

    }    

}Co[MAX];



int cmp(Node x,Node y)

{

    return x.rank < y.rank;

}



int main()

{

    scanf("%d",&t);

    while(t--)

    {

        scanf("%d %d %d",&n,&m,&q);

        for(i=0;i<m;i++)

            scanf("%I64d %I64d %I64d",&Co[i].u,&Co[i].c,&Co[i].l);

        sort(Co,Co+m);

    //    printf("\nafter once sort:\n");

     //    for(i=0;i<m;i++)

     //        printf("%lld %lld %lld %lld\n",Co[i].u,Co[i].c,Co[i].l,Co[i].rank);

             

        Co[0].rank = 0;            //此处改为1.错位?



        for(i=1;i<m;i++)

        {

            if(Co[i].u == Co[i-1].u)

                Co[i].rank = Co[i-1].rank+1;

            else

                Co[i].rank = 0;    //此处改为1,错误? 

        }

        

         sort(Co,Co+m,cmp);

    //    printf("\nafter twice sort:\n");

     //    for(i=0;i<m;i++)

     //        printf("%lld %lld %lld %lld\n",Co[i].u,Co[i].c,Co[i].l,Co[i].rank);

        ans[1] = Co[0].l;

        int Rank = 1;

        for(i=1;i<m;i++)

        {

            if(Co[i].rank == Co[i-1].rank)

                ans[Rank] += Co[i].l;

            else

            {

                ans[Rank] += ans[Rank-1];

                Rank++;

                ans[Rank] = Co[i].l;

            }

        }

        ans[Rank] += ans[Rank-1];

        

        printf("Case #%d:\n",++cnt);

        for(i=0;i<q;i++)

        {

            scanf("%d",&k);

            k = min(k,Rank);

            printf("%I64d\n",ans[k]);

        }

        

    }

    

    return 0;

}
代码君二,始终想不通注释处问题

 

 

 

 11. POJ 1200 Crazy Search

题意:给定一个字符串,由NC个不同的字符构成,问原串中长度为N的子串有多少种
分析:字符串Hash,将字符串直接对应成NC进制的N位数的一个整数,然后标记之。
题解:注意给每一个字符对应一个值,还有数组的大小是160W,注意细节。

#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 16000100

int N,NC,cnt,ans;

char str[M];

bool Table[M];

int ascii[256];



int main()

{

    while(~scanf("%d %d",&N,&NC))

    {

        scanf("%s",str);

        CLR(ascii,0);

        CLR(Table,0);

        cnt = 1,ans = 0;

        int len = strlen(str);

        for(int i=0;cnt<=NC;i++)

        {

            if(ascii[str[i]] == 0)

                ascii[str[i]] = cnt++;

        }

        for(int i=0;i+N<=len;i++)

        {

            int key = 0;

            for(int j=i;j<i+N;j++)

                key = key*NC  + ascii[str[j]];

            if(!Table[key])

            {

                Table[key] = 1;

                ans++;

            }

        }

        printf("%d\n",ans);    

    }

    return 0;

}

    
代码君啊,注意循环的细节啊,,坑死了啊

 

 

 

 
  

 12. POJ 3274 Gold Balanced Lineup

题意:对于一群牛,每个牛有自己对应的十进制数,转化为二进制数的话,每一位代表一种特性,问所有特性都相等的最大连续数是多少,发现我表述不清啊。具体看题吧。就是说有一个balance range的定义要搞清楚
分析:这题充分体现了hash你牛逼之处,我使用了BKDR和ELF两种HASH,时间在250ms。
题解:这题是个好题,我搞了很久。。终于算是搞出来了。这里还要处理一个额外的数组,这个也是看别人的题解知道的。
比如,测试数据 7 3      7 6 7 2 1 4 2



我们将它转化为二进制数

7    1 1 1

6    1 1 0

7    1 1 1

2    0 1 0

1    0 0 1

4    1 0 0

2    0 1 0



再进行处理得到



1    1 1 1

2    2 2 1 

3    3 3 2

4    3 4 2

5    3 4 3

6    4 4 3

7    4 5 3



如果这个时候我们再用一个数组,存放的是每一个行减去最右边那个数



0    0 0 0

1    0 0 0 

2    1 1 0 

3    1 1 0

4    1 2 0

5    0 1 0

6    1 1 0

7    1 2 0



可以看到第2行和第6行是一样的,这样我们可以说最大的balance range 是 4

为什么呢?

很显然,我们剪掉最右边的数,如果这个区间是满足balance range的,

那么从左到右肯定加的都是这个数,那么自然,全部减掉后,相当于没加,

那么自然如果这两行相等,说明必然是balance range,



这样,题目就转化为了求距离最远的两行的差值了。

这里关于Hash,主要是在解决冲突的问题上,如何有效得解决冲突是很重要的。。。
#include <cstdio>

#include <algorithm>

#include <cstring>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 999983

#define MAX 100005

int N,K,t;

int x[MAX][35];



struct HashTable

{

    int arr[MAX][35],Hash[M],ans;

    

/*    int Encode(int id)    

    {

        int i,tmp=0;

        for(i=0;i<K;i++)

            tmp=((tmp<<2)+(arr[id][i]>>4))^(arr[id][i]<<10);    

        tmp = tmp%M;

        if(tmp<0)    tmp=tmp+M;

        return tmp;

    }

*/

/*    int Encode(int id)

    {

        int seed[] = {37,131};

        int key = 0;

        for(int i=0;i<K;i++)

            key = key*seed[i&1] + arr[id][i];

        return (key&0x7fffffff) % M;

    }

*/

/*

    int Encode(int id)

    {

        int seed = 131;

        int key = 0;

        for(int i=0;i<K;i++)

            key = key*seed + arr[id][i];

        return (key&0x7fffffff) % M;

    }

*/    

    int Encode(int id)

    {

        int key = 0;

        int x = 0;

        for(int i=0;i<K;i++)

        {

            key = (key << 4) + arr[id][i];

            if((x = key & 0xF0000000L) != 0)

            {

                key ^= (x >> 24);

                key &= ~x;

            }

        }

        return (key&0x7fffffff) % M;

    }

    void insert(int id)

    {

        int key = Encode(id);

        int i = 0;

        while(Hash[key] != -1)

        {

            for(i=0;i<K;i++)

            {

                if(arr[id][i] != arr[Hash[key]][i])

                    break;

            }

            if(i == K)

            { 

                if(id - Hash[key] > ans)

                    ans = id - Hash[key];

                return;

            }

            key = (key+1)%M;

        }

        if(Hash[key] == -1)

            Hash[key] = id;

//        for(i=0;i<K;i++)

//            arr[key][i] = x[id][i];

    }

    

    void clear()

    {

        ans = 0;

        CLR(Hash,-1);

    }

}g;



int main()

{

//    freopen("1.txt","r",stdin);

    while(~scanf("%d %d",&N,&K))

    {

        g.clear();

        g.Hash[g.Encode(0)] = 0;

        for(int i=0;i<K;i++)

            x[0][i] = 0;

        

        for(int i=1;i<=N;i++)

        {

            scanf("%d",&t);

            for(int j=0;j<K;j++)

            {    

                g.arr[i][j] = t%2;

                t >>= 1;

                x[i][j] = x[i-1][j] + g.arr[i][j];

                g.arr[i][j] = x[i][j] - x[i][0];

            }            

            g.insert(i);        

        }

        printf("%d\n",g.ans);

    }

    return 0;

}
代码君,250ms

 

 
  

 

 13. POJ 3349 Snowflake Snow Snowflakes

题意:给定N(N<=10W)片雪花,每片雪花有六个花瓣(我姑且这样叫吧),问能否找出两片完全一样的雪花。
分析:哲理我也是灵光一闪想到了只有六片花瓣,果断sort排序然后字符串Hash搞之,自己瞎搞写了个HashTable解决了下冲突问题,2200ms+A掉了,1A的感觉其实还是相当爽的。
题解:其实我忽略了一个问题,就是如何判断两个雪花是否完全一样,,后来去看别人的题解才知道,要考虑顺时针和你时针的问题,我一个排序恰好没有这个问题了。。。。
后来发现的问题,如果再仔细考虑的话,确实存在这个问题。雪花的结构不能破坏啊。。。
比如像所有数的和,所有数平方的和,这种哈希方法不可行。

从小到大的方法也不可行。因为已经破坏了雪花的结构了





2

1 2 3 4 5 6

3 1 2 4 6 5



照道理说应该输出:No two snowflakes are alike.

 

主要问题 相同雪花判断,Hash函数设计,冲突问题解决。
POJ某人总结的作法:

1. 直接相加, 把(总和%大质数)为key.

2. 平方和相加, 把(总和%大质数)为key.

3. 从小到大的顺序, 对v[i]<<(i*3)依次异或, 然后模一个大质数作为key.(by hust07p43)

4. 六个数中非零数的积再乘上一个大质数,然后模一个100w上下的数。

自己拿随机数据测下来110w左右的效果最好,随机情况下数据量是10w的时候hash值相同的情况只有6k多个,几乎可以忽略。(by  killertom)

5. 依次对每个数异或所得的数作为key. (by archerstarlee)

6. (a[0] + a[2] + a[4])&(a[1] + a[3] + a[5]), 再模一个大质数. 中间的&还可以改成'|' 或者'^'.非常巧妙! 我采用'^'得到最快的719ms. (只对本题适用的hash方法)



其实最关键的就是要开放式寻址解决hash冲突, 不要以为hash就能解决问题了.

最后就是用getchar和gets来进行输入转换更为快速. G++比GCC快一些.



这里的话我强行用了BKDRHash,然后用了排序。

至于getchar读入的话,可以学习学习
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))

#define M 300007

int x[10],n;



struct HashTable

{

    int Hash[M],ans[M][6];

    bool flag;

    int BKDRHash()

    {

        int seed = 131;

        int key  = 0;

        for(int i=0;i<6;i++)

            key = key*seed + x[i];

        

        return (key&0x7FFFFFFF) % M;

    }

    

    void insert()

    {

        if(flag)

            return;

        int key = BKDRHash();

        while(Hash[key] != -1)

        {

            int i;

            for(i=0;i<6;i++)

            {

                if(x[i] != ans[key][i])

                    break;

            }

            if(i == 6)

            {

                flag = true;

                return;

            }

            key++;

        }

        Hash[key] = 1;

        for(int i=0;i<6;i++)

        {

            ans[key][i] = x[i];

        }

    }

    

    void init()

    {

        flag = false;

        CLR(Hash,-1);    

    }

}g;





int main()

{

    while(~scanf("%d",&n))

    {

        g.init();

        for(int i=0;i<n;i++)

        {

            for(int i=0;i<6;i++)

                scanf("%d",&x[i]);

            sort(x,x+6);

            g.insert();

        }

        if(g.flag)

            puts("Twin snowflakes found.");

        else

            puts("No two snowflakes are alike.");

        

    }

    

    return 0;

}
代码君,1A,2200ms+

 

  

 

 14. POJ 2503 Babelfish

 

题意:每一个火星文,对应一个翻译过来的单词,出入一个火星文,翻译为正常单词
分析:在没有学习Hash之前,一看这题想都不用想就会去写trie来A,实际上我也是这么干的
题解:这题用字符串Hash来写,不需要处理冲突,我觉得应该是数据比较弱。这也说明了,Hash并不一定是正确的,但是有时候真的很好用。这道题不解决冲突也A了,但是用起来确实有点虚

总的来说还是字符串Hash + 避免冲突
#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;



#define TABLE_SIZE 200003



struct Node {

   char key[12], fore[12];

   bool flag;

   Node () { flag = false; }

} table[TABLE_SIZE];



unsigned int BKDRHash(char *str)

{

    unsigned int seed = 131;

    unsigned int hash = 0;



    while (*str)

    {

        hash = hash * seed + (*str++);

    }



    return (hash & 0x7FFFFFFF) % TABLE_SIZE;

}



void insert(char *s1, char *s2)

{

    int pos = BKDRHash(s2), m = 0;



    while (table[pos].flag) {

        pos += 2 * (++m) - 1;

        if (pos >= TABLE_SIZE) pos -= TABLE_SIZE;

    }

    strcpy (table[pos].key, s2);

    strcpy (table[pos].fore, s1);

    table[pos].flag = true;

}



int main()

{

    char buff[50], s1[20], s2[20];



    while (gets(buff) && buff[0] != '\0')

    {

        sscanf (buff, "%s %s", s1, s2);

        insert (s1, s2);

    }



    while (scanf ("%s", s1) != EOF) {

        int pos = BKDRHash(s1), m = 0;

        bool flag = false;



        while (table[pos].flag) {

            if (strcmp(table[pos].key, s1) == 0) { printf ("%s\n", table[pos].fore); flag = true; break; }

            else {

                pos += 2 * (++m) - 1;

                pos = (pos >= TABLE_SIZE ? pos - TABLE_SIZE : pos);

            }

        }

        if (!flag) printf ("eh\n");

        getchar();

    }

    return 0;

}
代码君一,别人写的,正规做法,散列之后再考虑冲突问题
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;



#define CLR(a,b) memset(a,b,sizeof(a))

#define MAXN 100005

#define MOD 200003



typedef struct ENTRY

{

    char e[11];

    char f[11];

    int next;

}Entry;



Entry entry[MOD];

int cnt = 1;

int HashIndex[MOD];

char str[30];



int ELFHash(char* key)

{

    unsigned long h = 0;

    while(*key)

    {

        h = (h << 4) + (*key++);

        unsigned long g = h & 0Xf0000000L;

        if(g)    h ^= g >> 24;

        h &= ~g;

    }

    

    return h % MOD;

}



void find(char* f)

{

    int Hash = ELFHash(f);

    for(int k = HashIndex[Hash];k;k=entry[k].next)

    {

        if(strcmp(f,entry[k].f) == 0)

        {

            printf("%s\n",entry[k].e);

            return;

        }

    }

    printf("eh\n");

}



int main()

{

    while(gets(str))

    {

        if(*str == '\0')

            break;

        sscanf(str,"%s %s",entry[cnt].e,entry[cnt].f);

        int Hash = ELFHash(entry[cnt].f);

        entry[cnt].next = HashIndex[Hash];

        HashIndex[Hash] = cnt++;

    }

    while(gets(str))

        find(str);

        

    return 0;

}
代码君二,我的,没有考虑冲突的,也能过。
#include <cstdio>

#include <cstring>

#include <algorithm>

//#include <malloc.h>

using namespace std;

#define CLR(a,b) memset(a,b,sizeof(a))



struct Trie

{

    struct Node

    {

        int cnt;

        Node* next[26];

        char dir[12];

    };



    Node* root;



    void Init()

    {

        root = CreateNode();

    }



    Node* CreateNode()

    {

        Node* tmp = (Node*)malloc(sizeof(Node));

        for(int i=0;i<26;i++)

            tmp->next[i] = NULL;

        tmp->cnt = 0;

        return tmp;

    }



    void Insert(char* str,char* dir)

    {

        Node* p = root;

        while(*str)

        {

            int t = *str - 'a';

            if(p->next[t] == NULL)

                p->next[t] = CreateNode();

            p = p->next[t];

            str++;

        }

        p->cnt++;

        strcpy(p->dir,dir);

    }



    void Search(char* str)

    {

        Node* p = root;

        while(*str && p)

        {

            p = p->next[*str - 'a'];

            str++;

        }

        if(p && p->cnt)

            printf("%s\n",p->dir);

        else

            printf("eh\n");

    }

    

    void Delete(Node* rt)

    {

        if(rt == NULL)

            return;

        for(int i=0;i<26;i++)

        {

            if(rt->next[i])

                Delete(rt->next[i]);

        }

        free(rt);

    }

}Trie;



char s1[20],s2[20];



int main()

{

//    freopen("1.txt","r",stdin);

    Trie.Init();

    while(gets(s1) && *s1 != '\0')

    {

        int i;

        for(i=0;s1[i]!=' ';i++);

        s1[i] = '\0';

        strcpy(s2,s1+i+1);

        Trie.Insert(s2,s1);

    }



    while(~scanf("%s",s1))

    {

        Trie.Search(s1);

    }

    Trie.Delete(Trie.root);

    return 0;

}
代码君三,Trie,没什么好说的裸题

 

 
  

 15. POJ 3261 Milk Patterns

题意:实际上就是给定一个长度为N的字符串(这里用的是整型数组存放的)和一个k,在串中找到出现次数≥k的长度最长的子串
分析:多项式hash + 二分答案,处理一下judge,注意二分,然后结束
题解:这道题目正统的作法应该是后缀数组,无奈自己现在还不会,现在学习的是tao哥的多项式hash+二分来做,体会多项式hash的强大以及二分答案的快感。
 
  

正统作法 后缀数组, 学习的作法 多项式Hash + 二分答案
#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

#define x 123

#define maxn 20020

int n,k;

int s[maxn];

unsigned long long Hash[maxn], h[maxn], xp[maxn];

int rank[maxn];



int cmp(int a,int b)

{

    if(Hash[a] == Hash[b])    return a < b;

    else    return Hash[a] < Hash[b];

    

}

bool judge(int l)

{

    int c = 0;

    for(int i=0;i+l-1<n;i++)

    {

        rank[i] = i;

        Hash[i] = h[i] - h[i+l] * xp[l];

    }

    sort(rank,rank+n-l+1,cmp);

    for(int i=0;i+l-1<n;i++)

    {

        if(i == 0 || Hash[rank[i]] != Hash[rank[i-1]]) c = 0;

        if(++c >= k)    return true;

    }

    return false;

}



int main()

{

    while(~scanf("%d %d",&n,&k))

    {

        for(int i=0;i<n;i++)

            scanf("%d",&s[i]),s[i]++;

        h[n] = 0;

        for(int i=n-1;i>=0;i--)

            h[i] = h[i+1] * x + s[i];

        xp[0] = 1;

        for(int i=1;i<=n;i++)

            xp[i] = xp[i-1] * x;

        int l = 1, r = n;

        while(l <= r)

        {

            int m = (l + r) >> 1;

            if(judge(m)) l = m + 1;

            else         r = m - 1;

        }

        printf("%d\n",l-1);        

    }

    return 0;

}
代码君

 



你可能感兴趣的:(hash)