E - Prefix Equality —— 集合哈希
题意
给定一个长度为 n 的数列 A,数列 B。
有 m 次询问,每次询问给出两个位置 x, y:
- 数列 A 的前 x 个位置构成的集合 与 数列 B 的前 y 个位置构成的集合 是否完全相同?(集合不考虑元素顺序和元素重复)
1
≤
N
,
Q
≤
2
×
1
0
5
1 \leq N, Q \leq 2 \times 10^{5}
1≤N,Q≤2×105
1
≤
a
i
,
b
i
≤
1
0
9
1 \leq a_{i}, b_{i} \leq 10^{9}
1≤ai,bi≤109,
1
≤
x
i
,
y
i
≤
N
1 \leq x_{i}, y_{i} \leq N
1≤xi,yi≤N
思路
判断两个集合是否相同,可以将一个集合哈希成一个数,直接 O(1) 判断两个数是否相同。
因为不考虑两个集合是否相同,所以哈希的时候不能像 字符串哈希 那样进制类的哈希,而是采取元素相加取模的方式:
- 将一个数哈希成比较大的数,然后集合中的所有数相加就将这个集合哈希成了一个值。
而如何哈希能够将冲突减少到最小呢?这里是一种方法:
(
s
s
s 是哈希后的集合,
a
a
a 是集合中的一个元素,
P
P
P 是模数)
这道题就可以用这种方法 A 掉。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
#define endl '\n'
const int N = 200010, mod = 8128812800000059;
int T, n, m;
int a[N], f1[N], f2[N];
set<int> st;
signed main(){
Ios;
cin >> n;
int s = 0;
for(int i=1;i<=n;i++)
{
int x;cin >> x;
if(st.find(x) == st.end()){
s = (s + x * (x+1347)%mod * (x+9185)%mod) % mod;
}
f1[i] = s;
st.insert(x);
}
st.clear(); s = 0;
for(int i=1;i<=n;i++)
{
int x;cin >> x;
if(st.find(x) == st.end()){
s = (s + x * (x+1347)%mod * (x+9185)%mod) % mod;
}
f2[i] = s;
st.insert(x);
}
cin >> m;
while(m--)
{
int x, y;cin >> x >> y;
if(f1[x] == f2[y]) cout << "Yes\n";
else cout << "No\n";
}
return 0;
}
图像存储 —— dfs / bfs 路径哈希
题意
给定 n*m 的 01 矩阵,相邻的所有 1 构成一个连通块。
如果两个连通块通过上下左右平移可以完全重合,那么这两个连通块是同一种类的。
问,连通块的个数,连通块的种类数。
思路
连通块的个数好搞,bfs 或者 dfs 填充一下就行。
关键是如何判断两个连通块是不是同一种类的,这里做法是哈希 bfs 或者 dfs 的路径。
因为是从上到下,从左到右依次判断一个位置是否已经被标记(如果没有标记就递归标记这个连通块),所以每次第一个走的位置一定是每个连通块最左上角的位置。从这个位置出发,进行 bfs 或者 dfs 遍历连通块的所有位置,按照每一次遍历都有一个方向,将一个连通块遍历的所有方向记录下来便是这个连通块遍历的路径。
如果两个连通块相同的话,遍历路径一定相同;但是遍历路径相同,两个连通块却不一定相同。所以也要采取策略来减少冲突。
有两种遍历策略,dfs
和 bfs
。
DFS思路:
为什么会有冲突呢?
考虑这样的情况:可能一条路走到头,得到的方向序列为012012;还有可能是一条路走到头,得到方向序列0120,然后回溯过来走另一条路得到方向序列12。那么这两种图形的方向序列是相同的,但是图形却不相同。
为了避免这种情况,可以遍历走到头的情况,方向序列后面加个字符 #
。
此外,考虑这样的情况:从上到下依次走过(1, 2), (2, 2), (3, 2), (4, 2)位置,后面不能走了,然后回溯到 (2, 2) 位置,再走 (2, 1) 位置。这样得到的方向序列是 下下下左
;而如果有另一种图形,也是从上到下走过4个位置,但是回溯到(1, 2)位置,再走(1,1)位置。这样得到的方向序列也是 下下下左
。方向序列一样,但是图形不同。
为了避免这种情况,可以在方向序列后面每回溯一次加上一个 $
。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'
map<string,int> mp;
const int N = 2010, mod = 1e9+7;
int T, n, m;
int a[N][N];
int f[N][N];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int cnt, ans;
string s;
void dfs(int x, int y)
{
int flag = 0;
for(int i=0;i<4;i++)
{
int tx = x + dir[i][0], ty = y + dir[i][1];
if(tx < 1 || ty < 1 || tx > n || ty > m) continue;
if(f[tx][ty] || !a[tx][ty]) continue;
f[tx][ty] = 1;
flag = 1;
s += char(i + '0');
dfs(tx, ty, i);
s += '$';
}
if(!flag) s += '#';
}
signed main(){
// Ios;
while(cin >> n >> m && n && m)
{
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
{
f[i][j] = 0;
char c;cin >> c;
a[i][j] = c-'0';
}
}
mp.clear();
cnt = 0, ans = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
{
if(f[i][j] || !a[i][j]) continue;
++cnt;
f[i][j] = 1;
s = "";
dfs(i, j);
mp[s] = 1;
}
}
cout << cnt << " " << mp.size() << endl;
}
return 0;
}
BFS思路
每次遍历四个方向判断是否可以拓展,如果可以,加入队列,将此方向加到方向序列。
同样可能出现冲突,所以每次遍历四个方向之后,方向序列之后加上字符#
。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<string,int> mp;
const int N = 2010, mod = 1e9+7;
int T, n, m;
int a[N][N];
int f[N][N];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int cnt, ans;
string s;
void bfs(int stx, int sty)
{
queue<PII> que;
que.push({stx, sty});
while(que.size())
{
int x = que.front().fi, y = que.front().se;
que.pop();
for(int i=0;i<4;i++)
{
int tx = x+dir[i][0], ty = y+dir[i][1];
if(tx < 1 || ty < 1 || tx > n || ty > m) continue;
if(f[tx][ty] || !a[tx][ty]) continue;
f[tx][ty] = 1;
s += char(i + '0');
que.push({tx, ty});
}
s += '#';
}
}
signed main(){
// Ios;
while(cin >> n >> m && n && m)
{
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
{
f[i][j] = 0;
char c;cin >> c;
a[i][j] = c-'0';
}
}
mp.clear();
cnt = 0, ans = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
{
if(f[i][j] || !a[i][j]) continue;
++cnt;
f[i][j] = 1;
s = "";
bfs(i, j);
mp[s] = 1;
}
}
cout << cnt << " " << mp.size() << endl;
}
return 0;
}
一开始觉得如果搜索路径相同的话,两个连通块一定是相同的,wa了几发才发现不是这样的。
以后再有这样的情况就尽可能多的增加哈希限制,让冲突减少。