Acwing(二)

第一节
1、链表与邻接表
2、栈与队列
3、Kmp

一、链表

1、单链表 : 邻接表
邻接表作用 存储图和树
2、双链表 用来优化某些问题

e[N] 某个点的值
ne[N] 某个节点的next指针
他们用下标关联起来

最后一个元素的next指针指向空集 ne[n-1]=-1
单链表只能找到一个节点的下一个数,无法找到上一个数
注意:下标是从0开始的,0是第一个插入的点
第k个插入的点的下标是k-1

#include
using namespace std;
const int N=1e5+10;
//head 表示头节点的next指针 
//e[i]表示节点i的值
//ne[i]表示节点i的next指针
//idx是指针 表示当前已经用到了哪个点的空间 
int head,e[N],ne[N],idx;
void init(){
   head=-1;
   idx=0;
}
//将x插到头节点的位置 
void add_to_head(int x){
   e[idx]=x;
   ne[idx]=head;
   head=idx;
   idx++;
}
//将x插入到下标是k的点后面 
void add(int k,int x){
   e[idx]=x;
   ne[idx]=ne[k];
   ne[k]=idx;
   idx++;
} 
//将下标是k的后一个点删掉 
void remove(int k){
   ne[k]=ne[ne[k]]; 
} 

int main()
{
   int m;
   cin>>m;
   init(); 
   while(m--){
   char c;
   int k,x;
   cin>>c;
   if(c=='H'){
       cin>>x;
       add_to_head(x);
   }
   else if(c=='D'){
       cin>>k;
       if(k==0)
           head=ne[head]; //删除头节点 
   else    remove(k-1);
   }
   else if(c=='I'){
       cin>>k>>x;
       add(k-1,x);
   }
   } 
   for(int i=head;i!=-1;i=ne[i])
       cout<

二、双链表

l[N] 指向 N 前面的节点
r[N] 指向 N 后面的节点
e[N] 表示这个点的值
让下标是0 的点为head
下标为1的点为最后一个点

#include
using namespace std;
int m;
const int N=1e5+10;
int e[N],l[N],r[N],idx;
void init(){
    r[0]=1;//0表示左端点,l表示右端点 
    l[1]=0;
    idx=2;
}
//在下标为k的右边插入一个点x
//若要在下标为k的左边插入一个点x
//只需要调用 l[k] c就可以 
void add(int k,int c){
    
    e[idx]=c;
    r[idx]=r[k];
    l[idx]=k;
    l[r[k]]=idx;
    r[k]=idx;
    idx++;
} 
//删除第k个点 
void remove(int k){
    r[l[k]]=r[k];
    l[r[k]]=l[k]; 
}  

int main()
{
    cin>>m;
    init();
    while(m--)
    {
        string op;
        cin>>op;
        if(op=="L"){
            int x;
            cin>>x;
            add(0,x); //在最左侧的右侧插入一个点 
        }            //左端点是0 
        else if(op=="R"){
            int x;
            cin>>x;
            add(l[1],x);//在最右侧的左侧 
        }               //最右侧端点的是1 插入l[1]后面 
        else if(op=="D"){
            int k;
            cin>>k;
            remove(k+1);//idx从2开始 所以是k+1 
        }
        else if(op=="IL"){
            int k,x;
            cin>>k>>x;
            add(l[k+1],x);//k+1
        }
        else if(op=="IR"){
            int k,x;
            cin>>k>>x;
            add(k+1,x);
        }
    }
    for(int i=r[0];i!=1;i=r[i]){
        cout<

三、邻接表

n个单链表
把每个表的所有邻边都存起来

四、模拟栈

int stk[N],tt;
stk[++tt]=x;//插入
tt--;   //弹出

if(tt>0) not empty
else empty
stk[tt];//栈顶 

//int stk[N],tt;
//stk[++tt]=x;//插入
//tt--; //弹出

//if(tt>0) not empty
//else empty
//stk[tt];//栈顶 

#include
using namespace std;
const int N=1e5+10;
int main(){
    int stack[N],tt;
    int m;
    cin>>m;
    while(m--){
        string op;
        cin>>op;
        int k,x;
        if(op=="push"){
            cin>>x;
            stack[++tt]=x; //插入x 
        }
        else if(op=="pop"){
            tt--;//弹出栈顶元素 
        }
        else if(op=="query"){
            cout<0)//tt大于0 则stack不为空 
                cout<<"NO"<

五、模拟队列

////在队尾插入元素,在队头弹出元素
//int q[N],hh,tt=-1;
////插入
//q[++tt]=x;
////弹出
//h++;
////判断是否为空 
//if(hh<=tt)not empty
//else empty
////去除队头元素
//q[hh]; 队头 
//q[tt];队尾 


#include
using namespace std;
const int N=1e5+10;
int q[N],hh,tt=-1;
int main(){
    int m;
    cin>>m;
    string op;
    while(m--){
        cin>>op;
        int x,k;
        if(op=="push"){
            cin>>x;
            q[++tt]=x;  //在队尾加入一个元素 
        }
        else if(op=="pop"){
    
            hh++;  //队头++ 就是弹出队头 
        }
        else if(op=="query"){
            cout<

六.单调栈

对暴力`进行优化
题型 找到左边或者右边,第一个比它大或者比他小的数

#include
using namespace std;
const int N=1e5+10;
int a[N],s[N]; //s为栈 
int tt=0;
int main()
{
   int n;
   cin>>n;
   for(int i=1;i<=n;i++)
   {
       cin>>a[i];  
       while(tt&&s[tt]>=a[i])tt--;//tt代表栈不为空 
       if(tt)cout<=a[i]当前栈顶元素大于当前元素则把他弹出 
       else cout<<-1<<' '; //操作后如果栈不为空则栈顶元素就是第一个小于当前元素的元素
        
       s[++tt]=a[i];//再把当前元素入栈 
   }
   return 0;
}

七. 单调队列

同样是对暴力进行优化
应用: 求滑动窗口中的最大值和最小值

栈和队列就是删除没有用的元素,维护一个单调性

#include
using namespace std;
const int N=1e6+10;
int a[N],q[N];
int tt=-1,hh;

int main()
{
   ios::sync_with_stdio(false);
   cin.tie(0);cout.tie(0);
   int n,k;
   cin>>n>>k;
   for(int i=1;i<=n;i++){
       cin>>a[i];
   }
   for(int i=1;i<=n;i++){
       if(hh<=tt&&i-k+1>q[hh])hh++;
   //队列不为空,如果起点>q[hh] 队列中存的是下标
   //则需要将窗口向后移动一个 h++; 
       while(hh<=tt&&a[q[tt]]>=a[i])tt--;
//队列不为孔二 且队尾下标对应元素大于a[i]则弹出 
       q[++tt]=i;//操作结束把新下标入队 
       if(i-k+1>=1)cout<q[hh])hh++;
   //队列不为空,如果起点>q[hh] 队列中存的是下标
   //则需要将窗口向后移动一个 h++; 
       while(hh<=tt&&a[q[tt]]<=a[i])tt--;
//队列不为孔二 且队尾下标对应元素大于a[i]则弹出 
       q[++tt]=i;//操作结束把新下标入队 
       //注意先把这个树加进去再输出 
       
       if(i-k+1>=1)cout<

KMP 算法

S[N] P[M] S长串 P 短串

寻找原串中包含子串的数目
next[N];
next[N] 只和字串有关 预处理子串
以某一点为中点的后缀和前缀 最长相等的长度是多少


1 j i

p[1~j] = p[i-j+1~i];

*/

//7. KMP
//求Next数组:
// s[]是模式串,p[]是模板串, n是s的长度,m是p的长度
//for (int i = 2, j = 0; i <= m; i++)
//{
//  while (j && p[i] != p[j + 1]) j = ne[j];
//  if (p[i] == p[j + 1]) j++;
//  ne[i] = j;
//}

// 匹配
//for (int i = 1, j = 0; i <= n; i++)
//{
//  while (j && s[i] != p[j + 1]) j = ne[j];
//  if (s[i] == p[j + 1]) j++;
//  if (j == m)
//  {
//      j = ne[j];
//       匹配成功后的逻辑
//  }
//}

#include
using namespace std;
int n,m;
const int N=10010,M=100010;
char p[N],s[M];
int ne[N];
int ans;
int main()
{
    cin>>n>>p+1>>m>>s+1;
    for(int i=2;i<=n;i++){
        while(j&&p[i]!=p[j+1])
            j=ne[j];
        if(p[i]==p[j+1])
            j++;
        ne[i]=j;
    }
    for(int i=1;i<=n;i++){
        while(j&&s[i]!=p[j+1])
            j=ne[j];
        if(p[i]==p[j+1])
            j++;
        if(j==n){
            cout<

trie 字典树

在一个集合中快速查找和存储一些字符串

#include
using namespace std;
const int N = 1e5 + 10;
//son[N][27] 表示一共有N 个节点 每个节点最多
//有 26 个儿子 所以二维的值是27
//cnt[N] 表示以某个字母为结尾的单词数量
//idx 表示当前创建的空间
int son[N][27],cnt[N],idx;
int n;
string str;
void Insert() {
    int len = str.size();
    int p = 0;
    for (int i = 0; i < str.size(); i++) {
        int u = str[i] - 'a';
        //如果没有这个字母就创建一个
        if (!son[p][u])son[p][u]=++idx;
        //创建后改变p 的值 增加节点数
        p = son[p][u];
    }
    //单词全部存储完毕 则将cnt++
    cnt[p]++;
}
int Que() {
    int len = str.size();
    int p = 0;
    for (int i = 0; i < str.size(); i++) {
        int u = str[i] - 'a';
        //如果找到某个字母时发现没有 则return0
        if (!son[p][u])return 0;
        //将p变成他的儿子

        else p = son[p][u];
    }
    return cnt[p];//返回这个单词的数量

}
int main()
{
    cin >> n;
    while (n--) {
        char c;
        cin >> c>>str;
        if (c == 'I') {
            Insert();
        }
        else if (c == 'Q') {
            int ans=Que();
            cout << ans << endl;
        }

    }
    system("PAUSE");
    return 0;
}

最大异或对

将一个数的二进制逐个插入字典树
从最高位开始找 每次找这个数相反的支路
如果没有相反的支路就走本支路

#include
using namespace std;
const int N = 1e5 + 10;
const int M=31e5+10;
int son[M][2],a[N],idx;
int n,ans=-0x3f3f3f3f;
void Insert(int x)
{
    int p = 0;
    for (int i = 30; i >=0; i--)
    {
        if (!son[p][x >> i & 1]) {
            //x>>i&1 分离出一个数的每个二进制位
            son[p][x >> i & 1] = ++idx;
            //如果没有则开建立一个儿子节点
        }
        p = son[p][x >> i & 1];
        //如果有则p变成他的儿子节点
    }
}
int Search(int x)
{
    int p = 0;
    long long res=0;
    for (int i = 30; i >= 0; i--) {

        if (son[p][!(x>>i&1)]) {
            //如果有与他相反的那个支路走这条
            p = son[p][!(x>>i&1)];
            //结果加上 1左移动i位
            res += 1 << i;
        }
        else p = son[p][x>>i&1];
        //如果没有相反的则走这条相同的路
        //异或后为0 res 不变
    }
    return res;

}
int main()
{
    /*int a = 1, b = 2,c;
    c = a ^ b;
    cout << c << endl;*/
    //cout << M << endl;

    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    for (int i = 1; i <= n; i++)
    {
        Insert(a[i]);//插入字典树
    }
    for (int i = 1; i <= n; i++)
    {
        ans=max(ans,Search(a[i]));
        //搜索每个元素异或最大的支路值
    }
    cout << ans << endl;
    system("PAUSE");
    return 0;
}

并查集

1、将两个集合合并
2、询问两个元素是否在一个集合当中
基本原理:
用树的形式来维护一个集合。
每个树的根节点的编号为这个集合的编号
每一个节点都存储一下其父亲节点是谁P[x]表示x的父节点
问题1:如何判断树根? 若P[X]=x 则x是树根
问题2:如何求x的集合编号 while(p[x]!=x)x=p[x];
问题3:如何合并两个集合:若px是x集合的编号,py是y集合的编号
则 p[x]=y 把x集合合并到y集合当中

并查集的优化 : 路径压缩
遍历后 将所有的儿子节点都指向根节点

#include
using namespace std;
const int N = 1e5 + 10;
int p[N], idx;
int n, m;
int find(int x) {

    if (p[x] != x) //如果还没找到根节点 则继续往上找
        p[x]=find(p[x]);//找到后赋值给p[x]并且返回
                        // 注意找到后·赋值给p[x] 顺便做了路劲压缩
    //for (int i = 1; i <= n; i++)
    //  cout << p[i] << " ";
    //cout << endl;
    return p[x];
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        p[i] = i; //起初的时候每个数字是一个集合
                //初始化他们的根节点为本身
    }
    while (m--)
    {
        char c;
        int a, b;
        cin >> c >> a >> b;
        if (c == 'M') {
            p[find(a)] = find(b); //将a 直接并入b中
        } 
        else if (c == 'Q') {
            if (find(a) == find(b))//如果a b的根节点相同则属于一个集合
                cout << "Yes" << endl;
            else cout << "No" << endl;
        }

    }
    //system("PAUSE");
    return 0;
}

连通块中点的数量 837

#include
using namespace std;
const int N = 1e5 + 10;
int p[N], cnt[N];
int find(int x) {
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        p[i] = i;
        cnt[i]++;
    }
    while (m--) {
        string op;
        int a, b;
        cin >> op;
        if (op =="C") {
            cin >> a >> b;
            if (find(a) != find(b))
            {
                int num = cnt[find(a)];

                p[find(a)] = find(b);
                cnt[find(b)] += num;
            }
        }
        else if (op == "Q1") {
            cin >> a >> b;
            if (find(a) == find(b))
                cout << "Yes" << endl;
            else cout << "No"<> a;
            cout << cnt[find(a)] << endl;
        }


    }

    return 0;
}

费解的开关

1、 按的顺序可以任意
2、 按偶数次是不变的所以每个格子最多按一次按两次没用
3、从第一行开始 第一行操作完不改变 则 第二行被第一行唯一确定

核心:每一行开关的操作完全被上一行灯的亮灭状态所决定

问题
1;如何枚举第一行的操作
位运算二进制枚举
2^5=0~31
00000 00001
....~11111 16+8+4+2+1=31

for(int i=0;i<32;i++)
{
看第k位是否需要操作
i >> K&1;
}

2: turn(x,y)
根据偏移量 写开关变换的状态
3:时间复杂度

#include
using namespace std;


string s[6],back_up[6];
const int N=6;  
//char s[N][N],back_up[N][N]; 
int n;                           
int dx[5]={0,-1,0,1,0};//枚举五个方向 
int dy[5]={-1,0,1,0,0};

void turn(int x,int y)
{
    for(int i=0;i<5;i++){
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(xx<0||xx>4||yy<0||yy>4)
            continue;
        if(back_up[xx][yy]=='0')
                back_up[xx][yy]='1';
        else back_up[xx][yy]='0'; 
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    while(n--){
        string s[5];
        for(int i=0;i<5;i++)
            cin>>s[i];
        int ans=10;
        for(int op=0;op<32;op++){ //注意这里枚举的是操作数目 而不是第一行的状态 
            //memcpy(back_up,s,sizeof(s));
            for(int i=0;i<5;i++)
                back_up[i]=s[i];  //用一个数组作为拷贝数组 用来改变原数据 
            int step=0;           
            for(int i=0;i<5;i++){

            if(op>>i&1)
            {
                step++;
                turn(0,i);
            }
        }
            for(int i=0;i<4;i++){

                for(int j=0;j<5;j++){

                    if(back_up[i][j]=='0'){
                        step++;  //这一行的为0 下一行必须改变 
                        turn(i+1,j);
                    }

                }
            }
            bool dark=false;
            for(int i=0;i<5;i++){
                if(back_up[4][i]=='0')
                    {
                        dark=true;
                        break;
                }
            }
            if(!dark)
                ans=min(ans,step);
            for(int i=0;i<5;i++)
                back_up[i]=s[i];
        }
        if(ans>6)
            cout<<-1<

飞行员兄弟

  1. 每个开关只按一次
    2.顺序是没有关系的

解题思路
1、枚举所有方案
2、按照方案对所有灯泡进行操作
3、判断是否全亮->记录方案

#include
using namespace std;
const int N = 5;
char g[N][N], backup[N][N];
typedef pair PII;
int get(int x, int y) {
    return x * 4 + y;  // 将 二维数组下标 转化为 0~15
}
void turn_one(int x, int y) {
    if (g[x][y] == '+')
        g[x][y] = '-';
    else g[x][y] = '+';
}
void turn_all(int x, int y) {
    for (int i = 0; i < 4; i++) {
        turn_one(x, i);// 行操作
        turn_one(i, y);//列操作
    }
    turn_one(x, y);// 由于x y 这个点操作了 两次 所以再操作一次 将其恢复为操作一次
}
int main()
{
    for (int i = 0; i < 4; i++)
        cin >> g[i];
    vector res;  //记录最终答案res
    for (int op = 0; op < (1 << 16); op++) {//枚举每一种状态 一共2^16种 1表示要按下这个开关一次
        vector temp;//每次枚举新建一共存储方案
        memcpy(backup, g, sizeof(g));//拷贝保留原数据


        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (op >> get(i, j) & 1) { //二维枚举 每当遇到一共1 就是一次操作
                    temp.push_back(make_pair(i, j));//没操作一次 记录再方案中
                    turn_all(i, j); //进行操作
                }
            }
        }
        bool has_closed = false;  //判断结果
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (g[i][j] == '+')
                    has_closed = true;
            }
        }
        if (has_closed == false) {

            //起初的res 是空的 所以要加入这个条件 让此时的res被temp赋值
            //后面如果res的操作数是比temp多的 则更新 res
            if (res.empty() || res.size() > temp.size())
                res = temp;
        }
        memcpy(g, backup, sizeof(g));

    }
    cout << res.size() << endl;
    vector::iterator iter = res.begin(); //迭代器  遍历输出res
    for (iter; iter != res.end(); ++iter) 
        cout << iter->first + 1 << ' ' << iter->second + 1 << endl;


    return 0;
}

/*

翻硬币

开始用了DFS 只能过一半
后来发现了规律,每个硬币反两次则无效
只需要从头到尾枚举一遍 不一样就翻一次 就可
*/

#include
using namespace std;
string s1, s2;
int len;
int ans = 0x3f3f3f3f;
void turn(int num)
{
    if (s1[num] == '*')
        s1[num] = 'o';
    else s1[num] = '*';
    if (s1[num + 1] == '*')
        s1[num + 1] = 'o';
    else s1[num + 1] = '*';
}
int main()
{
    cin >> s1 >> s2;
    len = s1.length();
    string backup = s1;
    int step = 0;
    for (int i = 0; i < len - 1; i++)
    {
        if (s1[i] != s2[i])
        {
            turn(i);
            step++;
        }
        if (s1 == s2)
            break;
    }
    cout << step << endl;
    system("PAUSE");
    return 0;
}

食物链

余1: 可以吃根节点
余2: 可以被根节点吃
余0:与根节点同类

余2吃余1 余0吃余2
所以%3余数相同的则是同一类 (关键)
带权并查集精髓总结:只要两个元素在一个集合里面,通过它们与根节点的距离就能知道它们的相对关系。

#include
using namespace std;
const int N = 5e4+10;
int n,k;
int p[N],d[N];
int find(int x) {
    if (p[x] != x) {
        int t = find(p[x]);
        d[x] += d[p[x]];
        p[x] = t;
    }
    return p[x];
}
int main()
{
    cin >> n >> k;
    int res = 0;
    for (int i = 1; i <= n; i++)
        p[i] = i;
    while (k--)
    {
        int t, x, y;
        cin >> t >> x >> y;
        if (x > n || y > n)res++; //如果有的点大于了n 则是假话
        else {
            int px = find(x), py = find(y);// px 是x的根节点 y同理
            if (t == 1) {
                if (px == py && (d[x] - d[y]) % 3 != 0)//px==py 则x y 在同一个集合中
                     d[x]%3 应该 和d[y]%3 相等 这样x y 才是同类 -> (d[x]-d[y])%3==0
                    res++;
                else if (px != py) {
                    p[px] = py;   //如果不等则x y不在一个集合中
                    d[px] = d[y] - d[x];//d[x]+? %3 =d[y]%3 -> ?(d[px])=d[y]-d[x] 要使x y是同类则满足这个方程
                }//可以画图
            }
            else {
                if (px == py && (d[x] - d[y] - 1) % 3)res++;//如果px=py 则x y在同一集合 x吃y 则d[x]%3 -d[y]%3==1 ->(d[x]-d[y]-1)%3=0;
                else if (px != py) {
                    p[px] = py; //如果不在同一集合 则把x并入y集合中 (d[px]+? )%3=d[y]%3+1;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }

    }
    cout << res << endl;
    system("PAUSE");
    return 0;
}

堆: 手写堆

一.堆操作

  1. 插入一个数
    2.求这个集合当中的最小值
    3.删除最小值
    上面三个是STL 中可以实现
  2. 删除任意一个元素
    5.修改任意一个元素
    后两个操作只有手写堆才可以实现
    二.堆描述
    堆是完全二叉树 除了最后一层节点之外 上面所有节点都是满的
    最后一个节点从左到右排布

三.堆的构造:
堆的一个点小于等于左右儿子(左右儿子大小不一定)
根节点为堆的最小值
注意下标从1开始

四.堆存储
使用一个一维数组存储
x的做儿子: 2x
x的右儿子:2x+1;

五.核心操作
down

up

heap 堆 size 堆的大小 
1. 插入操作 
heap[++size]=x; up(size);
2.求当前堆的最小值 
heap[1]  ,堆顶一定是最小的 
3. 删除最小值 
用整个堆的最后一个元素覆盖掉堆顶元素 size-- 再down
(因为一维数组删除头非常困难,但是删除尾部很容易) 
heap[1]=heap[size];size--; dowx(1);
4. 删除任意一个元素
heap[k]=heap[size];size--;down(k);up(k); //只会执行down up 其中一个 
5.修改任意一个元素
heap[k]=x;down(k);up(k); 


void down(int u){
   int t=u;
   如果有左儿子 且 左儿子小于顶点 则把左儿子变成顶点 
   if(u*2<=size&&h[u*2]h[u]){
       swap(h[u/2],h[u]);// 如果大了就交换 
       u/=2; //每次u往上走 
   }
}

例1 堆排序

ph[]从下标到堆里 hp[]从堆到下标里 
#include
using namespace std;
const int N=1e5+10;
int h[N],size1;
int n,m;
void down(int u)
{
    int t=u;
    if(u*2<=size1&&h[u*2]>n>>m;
    for(int i=1;i<=n;i++)
    cin>>h[i];
    size1=n;
    for(int i=n/2;i;i--)
        down(i);
    while(m--)
    {
        cout<

例2: 模拟堆

#include
using namespace std;con1
const int N=100010;
int h[N],ph[N],hp[N],cnt;
void heap_swap(int a,int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a],hp[b]);
    swap(h[a],h[b]);
}
void down(int u){
    
    int t=u;
    if(u*2<=cnt&&h[u*2]>n;
    while(n--){
        string op
        int k,x;
        cin>>op;
        int k,x;
        if(op=="I"){
            cin>>x;
            cnt++;
            m++;
            ph[m]=cnt;
            hp[cnt]=m;
            h[cnt]=x;
            up(cnt);
        }
        else if(op=="PM")cout<>k;
        k=ph[k];
        heap_swap(k,cnt);
        cnt--;
        up(k);
        down(k);
    }else {
        cin>>k>>x;
        k=ph[k];
        h[k]=x;
        up(k);
        down(k);
    }
    
    return 0;
} 

哈希表 (散列表)

一、 存储结构
1.开放寻址法
2.拉链法

二、 字符串哈希方式

大范围的集合 映射到 从0~N

x mod 1e5 模要取一个质数 (冲突的概率小)
可能会产生冲突

1.拉链法:开一个一维数组 存储哈希的值

添加 找h(x) 插入到链上
查找 找h(x) 遍历链看有没有

  1. 开放寻址法
    1.添加 先找到h[k] 如果h[k] 有人 则往后找 直到没有人

2.查找 第k个坑位开始 从前往后找
如果当前位置有人,是x 则找到
如果当前位置有人,不是x 则继续找下一个
如果当前位置没人,则x不存在

hash表的删除一般不会直接删除 一般都是 把删除的元素做特殊的标记
而且一般不会用到删除操作

开放寻址法的核心操作
find函数:如果x在hash表中存在,则返回x的位置
如果x不存在则返回x应该存储的位置

#include
using namespace std;
const int N=2e5+10;
int mod=2e5+3;
int null =0x3f3f3f3f;
int h[N];
int find(int x)
{
    int t=(x%mod+mod)%mod;//为了防止余数是负数
    while(h[t]!=null&&h[t]!=x){//如果这个坑是空了 就不存在x了
                                //如果这个坑==x了 那就找到了
        
        t++; //继续找下一个坑
        if(t==N) //如果到末尾了 那就从头开始找
            t=0;
    }
    return t; //返回值是 x应该放的位置 或者是找到x的位置
}
int main()
{
    int n;
    cin>>n;
    memset(h,0x3f,sizeof(h));//首先将数组置为null 作为判断是否为空的标志
    while(n--){
        string op;
        int x;
        cin>>op>>x;
        if(op=="I"){
         //  h[x]=find(x); 
           h[find(x)]=x; // 返回的是x应该放的位置  让哈希表中的这个位置=x
        }
        else {
            if(h[find(x)]==null)//返回的值如果是空 则没有找到
                cout<<"No"<

二. 字符串哈希

字符串前缀哈希法

核心 在p 进制的角度将一个字符串看成是一个数字

如何来定义每一个前缀的哈希
将字符串看成是P进制数
不要某一个字符映射成0 因为0的p进制还是0
假定不存在冲突 不考虑冲突

经验值 p进制为 p=131, mod 为2^64;
这里用usigned long long 来实现 溢出就是对mod取模

核心操作:
1.hash的处理
h[i]=h[i-1]p+str[i];
2.求 l 到 r 之间的字符串
h[R]-h[L]
p^(R-L+1)
R-L+1=R-1-(L-2);
h[L-1] 从 p^0 ~ p^(L-2)
h[R] 从 p^0 ~p^(R-1)

预处理一个数的n次方

int p[N];
p[0]=1; //p的0次方为1;
for(int i=1;i<=n;i++)
p[i]=p[i-1]*p;

#include
using namespace std;
typedef unsigned long long ull;
int p=131;// 131 是 经验值
const int N=1e5+10;
ull pow1[N],h[N];// unsigned long long 溢出 就是mod 2^64  
char str[N];
ull hash1(int l,int r)
{
    return h[r]-h[l-1]*pow1[r-l+1]; //核心操作
    //求出l~r 区间内的hash值
}
int main()
{
    int n,m;
    cin>>n>>m>>str+1;//注意从下标1开始存储字符
    pow1[0]=1; // p的零次幂等于1
    for(int i=1;i<=n;i++)
    {
        pow1[i]=pow1[i-1]*p;//对p的次方 进行预处理操作
    }

    for(int i=1;i<=n;i++)
    {
        h[i]=h[i-1]*p+str[i];//核心操作 得到每个字符的hash值
    }
    while(m--){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(hash1(l1,r1)==hash1(l2,r2))//如果两个区间的hash值相等 则两个字符串相同
        cout<<"Yes"<

重点 C++的STL

vector
变长数组
倍增的思想
系统为某一程序分配空间时,所需时间与空间大小无关
与申请次数有关

#include

int main()
{
   vector a;// 定义一个vector
   vector a(10);//定义一个长度为10的vector
   vector a(10,3);//长度为10 每个元素为3

   vector a[10];//定义一个vector数组
   
   
    注意 size();和empty();是所有容器都有的函数
     时间复杂度为O(1); 
    a.size();//vector中元素的个数
    a.empty();//vector是否为空 
    
    并不是所有容器都有clear();比如队列就没有 
    a.clear(); //清空vector 
    
    a.front(); //返回vector的第一个数 .
    a.back();//返回vector的第二个数
    
    a.push_back();//向vctor的最后插入一个树 
    a.pop_back();//删除vector的最后一个树
    
    //迭代器
     a.begin();// vector的第0个数 a[0]
     a.end();//vector的最后一个数的后面一个数a[a.size()];
     
    遍历方式
    for(int i=0;i<10;i++)a.push_back(i);
    
    1.for(int i=0;i::iterator iter=a.begin();i!=a.end();i++)
    cout<<*i<<" ";
    cout< a(10,3);//长度为10 每个元素为3
   vector b(10,3);
   if(a==b)
       cout<<"a=b"< p;
p.first;//取得第一个元素 
p.second;//取得第二个元素 
支持比较运算 按照字典序 以 first 为第一关键字,以second为第二关键字

p=make_pair(10,"yxc");
C++11 :p={20,"abc"};

pair > //有三个属性时
与结构体相比 可以自带比较函数
 

 
string
字符串
size();
empty(); 
clear();
length();// 作用和size一模一样 
支持加减
string a="WD";
a+="def";//加上一段字符串 
a+="b";   //加上一个字符
 
cout<(); 

priority_queue
优先队列(堆)
 push();//向堆中插入一个元素 
 top();//返回堆顶元素 
 pop();//弹出堆顶元素
 
  一般默认大根堆 

也无clear函数 
priority_queue pq;
 

如果小变成小根堆
1. 插入的时候直接插入-x
2. 定义直接定义成小根堆
 priority_queue,greater >pq;
  




stack
栈 
也无clear 函数 
push();//向栈顶插入一个元素 
top();//返回栈顶元素 
pop(); //弹出栈顶元素 


deque 加强版的vector 
双端队列 
队头队尾都可以插入删除
(加强版的vector) 
size();
empty();
clear();
 front();//查询第一个元素 
 back();//查询最后一个元素 
 push_back();//向队尾插入一个元素 
 pop_back();//弹出队尾元素 
 push_front();//向队首插入一个元素 
 pop_front();//弹出队首元素 
 支持随机寻址
 begin();
 end(); 
 deque功能十分强大 但是速度比较慢
  



set,map,multiset,multimap
基于红黑树实现的,动态的维护一个有序的序列 
红黑树是平衡二叉树的一种 
set s;//不可语有重复元素 
multiset ms; 可以有重复元素 
size();
empty();
clear();
insert();//插入一个数
find();//查找一个数
 count();//返回一个数的个数 set为1
 erase():
  (1)如果输入一个数x 则删除所有x
  (2)如果输入一个迭代器,则删除这个迭代器
set的核心操作: 
lower_bound();返回大于等于x的最小的迭代器 
upper_bound(); 返回大于x的最小的迭代器 
如果没有则返回end();
 
map  
multimap

insert();//输入的参数是一个pair 
erase();//输入的参数是pair或者迭代器

 
 可像数组一样使用map
 map m;
 a["WD"]=1;
 cout< s;
~,&^|;
>>,<<
==,!=;
[];
count();返回有多少个1 
set(); 把所有为置为1
set(k,v); 把第k位变成v
reset();把所有位置0

你可能感兴趣的:(Acwing(二))