daimayuan第三课(哈希,堆)
1:哈希
建立:拉链法:
a:数组
#include <bits/stdc++.h> using namespace std; const int md = 1e9; int h[1000],idx,ne[1000],e[1000]; void add(int x) { int y = (x % md + md) % md; e[idx] = x; ne[idx] = h[y]; h[y] = idx ++; } int main() { memset(h,-1,sizeof h); int n; scanf("%d",&n); add(n); }
b:二维vector
2:字符串哈希
哈希函数 hash(s) = ((c1*pow(base,0)+c2*pow(base,1)+....+cn*pow(base,n-1))%p;
base 一般是大于ci的最大值,p和base都为质数且大于其给的范围,
同时可以用前缀和得到任意一段s的子串的哈希值
那么子串哈希hash(l,r) = (a[r] - a[l - 1] * base ^(r - l + 1) ) % p;
注意:为了防止偶然性可以用双哈希
3:对顶堆
所谓对顶堆,有点像双栈对顶那样,一个是小根堆,另一个是大根堆。假设 g 是大根堆,l是小根堆,两堆中间的元素,左边都是小于它的,且 g.top() 是小于它的最大值,右边都是大于它的,且 l.top() 是大于它的最小值
那么对顶堆是干嘛用的呢?
举一个例题:
给定N个数字,求其前ii个元素中第K小的那个元素。
我们很容易想到用堆来维护这个单调递增的序列,如果使用数组实现的话,我们直接输入数组下标为K的元素即可。但我们使用的是堆,它的实现方式——优先队列是不支持任意点访问的,那么我们就无法进行单点查询。引申对顶堆的概念,我们可以这样解决问题:
优先队列虽然不支持任意点访问,但可以用O(1)的时间查询出堆顶元素,也就是说,我们只能通过维护对顶堆中两个堆的堆顶元素来进行单调性的维护。怎么办呢?
原理很简单:根据数学中不等式的传递原理,假如一个集合A中的最小元素比另一个集合B中的最大元素还要大,那么就可以断定:A中的所有元素都比B中元素大。所以,我们把小根堆“放在”大根堆“上面”,如果小根堆的堆顶元素比大根堆的堆顶元素大,那么小根堆的所有元素要比大根堆的所有元素大。
回到这个问题:我们要求第K小的元素,那么我们把大根堆的元素个数限制成K个,前K个元素入队之后,每个元素在入队之前先与堆顶元素比较,如果比堆顶元素大,就加入小根堆,如果没有的话,把大根堆的堆顶弹出,将新元素加入大根堆。这样就维护出一个对顶堆。它的作用在于找出第K小的元素。
同理,对顶堆还可以用于解决其他“第K小”的变形问题:比如求前i个元素的中位数等。
1:背包
Applese有1个容量为v的背包,有n个物品,每一个物品有一个价值ai,以及一个大小bi
Applese觉得这个题依然太菜,于是他把这个问题丢给了你
当物品数量为偶数时,中位数即中间两个物品的价值的平均值
输入描述:
第一行三个数v, n, m,分别代表背包容量,物品数量以及需要取出的物品数量
输出描述:
仅一行,代表最大的中位数
题解:
关于这道题,我们先来看看m是奇数怎么做。
很明显,我们可以考虑枚举中位数,然后判断其是否可行即可。这样,我们先对所有物品按价值从小到大排序。
那么,如果一个物品可以作为中位数,当前仅当存在一种方案,从这个物品的左边选m/2个物品,再从它右边选个物品,他们的容积和加上该物品的容积小于等于背包容积v。那么,我们明显只需要贪心去取,每次取最小的几个即可。
我们从i号点左/右取个物品的最小容积和。
对于这个,我们直接开个优先队列,使得优先队列的元素是当前最小的个元素即可。
那么,枚举i时,我们只需要判断下加上左右是否小于等于v,即可。
偶数
我们发现,其实,我们可以把它看做我们从左边的中位数的左边拿m/2--1个物品,再从右边的中位数的右边拿m/2个物品二分不断往右靠
#include <bits/stdc++.h> using namespace std; typedef long long ll; struct Now{ ll vaule; ll weight; }a[100001]; bool cmp(Now a,Now b) { return a.vaule < b.vaule; } int s1[100001],s2[100001]; bool check(int res,int i,int v) { return s1[i - 1] + s2[res] + a[i].vaule <= v; } int main() { int v,n,m; scanf("%d %d %d",&v,&n,&m); for(int i = 1;i <= n; i ++) { scanf("%lld %lld",&a[i].vaule,&a[i].weight); } priority_queue<ll> q; int x = m & 1; m >>= 1; sort(a + 1,a + n + 1,cmp); for(int i = 1;i <= n; i ++) { q.push(a[i].weight); s1[i] = s1[i - 1] + a[i].weight; if(q.size() > m - 1 + x) { s1[i] -= q.top(); q.pop(); } } while(q.size()) q.pop(); for(int i =n; i >= 1;i --) { q.push(a[i].weight); s2[i] = s2[i + 1] + a[i].weight; if(q.size() > m) { s2[i] -= q.top(); q.pop(); } } if(x) { ll ans = 0; for(int i = m + 1;i <= n - m;i ++) { if(s1[i-1] + s2[i + 1] + a[i].weight <= v) ans = a[i].vaule; } printf("%lld\n",ans); } else{ ll ans = 0; for(int i = m; i <= n- m; i ++) { ll l = i + 1,r = n-m + 1; //int l=i+1,r=n-m+1; while(l<=r){ int mid=l+r>>1; if(s1[i-1]+s2[mid]+a[i].weight<=v)l=mid+1; else r=mid-1; } if(r > i) ans = max(ans,a[i].vaule + a[r].vaule); } printf("%lld\n",ans/2); } }
注意二分的边界,一个向上去取整一个向下取整。
2.小蜗的疑问
现在给你两个字符串a和b,a和b都由小写字母构成。
小蜗想知道字符串b在a中出现了几次(出现位置可以重叠)。
输入格式
第一行两个整数n,m,分别表示a和b的长度。
接下来两行,给出字符串a和b。
输出格式
输出一个数,表示答案
#include <bits/stdc++.h> using namespace std; const int base1 = 101,p = 9999971,base2 = 131,p1 = 9999973 ; char a[200011],b[200011]; int ha[200011],hb[200011],c[200011],c1[200011],ha1[200001],hb1[200001]; int main() { int n,m; scanf("%d %d",&n,&m); scanf("%s %s",a + 1,b + 1); c[0] = 1; c1[0] = 1; for(int i = 1;i <= 200000; i ++) { c[i] = c[i - 1] * base1 %p; c1[i] = c1[i - 1] * base2 % p1; } int res = 0; for(int i = 1;i <= n;i ++) { ha[i] = (ha[i - 1] * base1 + a[i] - 'a' ) % p; ha1[i] = (ha1[i - 1] * base2 + a[i] - 'a') % p1; } for(int i = 1;i <= m; i ++) { hb[i] = (hb[i - 1] * base1 + b[i] - 'a' ) % p; hb1[i] = (hb1[i - 1] * base2 + b[i] - 'a') % p1; } int ans = 0; for(int i = 1;i + m - 1 <= n; i ++) if((ha[i + m - 1]- 1LL * ha[i - 1] * c[m] % p + p ) % p == hb[m] && (ha1[i + m - 1] - 1LL * ha1[i - 1] * c1[m] % p1 + p1) % p1 == hb1[m]) { ans ++; } printf("%d\n",ans); }
3.动态中位数
中位数是按顺序排列的一组数据中居于中间位置的数。
现在依次给你nn个数a1,a2,…,an(保证n为奇数),你需要给出读入到第1,3,5,…,n 个数时,当前的中位数是多少。
输入格式
第一行一个整数n。
接下来一行共n个数。
输出格式
输出(n+1)/2个数,表示答案。
#include <bits/stdc++.h> using namespace std; int n; priority_queue<int> q; priority_queue<int ,vector<int> ,greater<int> > p; int a[100010],b[100010],idx = 1; int main() { scanf("%d",&n); for(int i = 1;i <= n;i ++) { scanf("%d",&a[i]); } q.push(a[1]); for(int i = 2;i <= n;i ++) { if(a[i] <= q.top()) q.push(a[i]); else p.push(a[i]); if(q.size() - p.size() == 3) p.push(q.top()),q.pop(); if(p.size() - q.size() == 1) q.push(p.top()),p.pop(); if(i % 2 == 0) printf("%d ",q.top()); } printf("%d\n",q.top()); }