我们先回忆一下字符串哈希。
平时的十进制其实就是字符串哈希,对于123456789,都是用hash[i]=hash[i-1]*10+i算出来的。如果我们不用递推式,其实就是个每位一个权值10^i,那么对于该位的值就是10^i+i。
其实字符串哈希就是将字符串看成一个base位进制的数,每位到base了才能进一位,每位的值都是base^i+该位的值。
那么如果我们要改变其中一位,比如把123456789的第三位改成7,也就是变成127456789,其实就是把123456789-3*10^3+7*10^3。
然后我们回到题目。
如果a,b在第i个图连通,那么就是find(a)=find(b),也就是a,b在第i个图里的祖先是同一个。如果要a,b在所有图里连通,也就是让所有图里的find(a)=find(b)。我们单独看a。a的祖先应该是fa[a]1,fa[a]2.....fa[a]d,再看b,fa[b]1,fa[b]2....fa[b]d。容易知道每一位a,b的祖先都应该是相同的才能说明a,b在所有连通块连通。
在每一次操作下,我们最多只会影响a,b的一个图的祖先节点。我们考虑怎么维护每个点在每个图的父亲节点并且可以单独修改其中一个值。很容易想到这就是d个字符组成的字符串了。由于我们每次都要比较整个字符串是否相等,我们可以将字符串哈希。单点修改就用上面说的那样就行了。
连通块可能是这样,但实际上我们并不想知道每个点的直接父亲是谁,而是想知道他们的整个连通块的祖先是谁,因为这样才能比较a,b是否在同一连通块,如果是看直接父亲则是不行的。
所以每次连接两个连通块的时候,我们就只记录祖先节点。比如a的祖先节点是b,现在要连接a和连通块x,就让x里所有的节点祖先节点赋值为b。这里可以发现,每次合并的时候都要遍历一个块的所有节点,我们考虑启发式合并,只遍历节点小的块,让小的块每个点祖先节点为大的块的祖先节点。
还是如上面那个图。如果d个图里这些点都是这样的连通块,我们会发现他们的哈希字符串都是一样的,我们可以用map来记录每个字符串的个数cnt。如果有x个字符串相同,也就是表示这x个点在所有图里都是连通的。我们考虑加点和删点的影响(删点是因为合并的时候,size小的块会重新赋值祖先节点)。
加点:对于bajtocja来说,因为题目要求的是有序的且包括本身和本身((x,x)这种first=second)的情况。我们假设已经有x个相同字符串,现在要加一个新点y,那么当y是first的情况会和x都组成对,同理,y是second的情况也有x对。最后(y,y)也有一对,所以是2*x+1.对于另一题因为是无序的所以(x,y)和(y,x)是一种情况,也就是说y不分first和second了,不能乘2,同时不包括本身了,不能加1,最近贡献就是x。
删点:就是加点的逆操作。加点前是x-1个字符串,这时候加的贡献是2*(x-1)+1,也就是2*x-1,我们要减掉,另一题就是x-1。
对于哈希的方法不唯一,一开始学的随机函数赋权值...能ac的哈希都是好哈希。
图计算代码:
用快读的时候都要用快读...
#include<bits/stdc++.h>
using namespace std;
//#define int unsigned long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=5e4+10;
const int mod=1e9+7;
#define fi first
#define se second
#define ls u<<1
#define rs u<<1|1
const int D=105;
inline int read(){
int res = 0, ch, flag = 0;
ch = getchar();
if(ch==EOF)
exit(0);
if(ch == '-') //判断正负
flag = 1;
else if(ch >= '0' && ch <= '9') //得到完整的数
res = ch - '0';
while((ch = getchar()) >= '0' && ch <= '9' )
res = res * 10 + ch - '0';
return flag ? -res : res;
}
int rt[D][N];
vector<int> t[D][N];
ull ha[N],val[D];
ll ans,d,n,m,q;
unordered_map<ull,int> cnt;
ull fac[D];
void init(){
mt19937_64 rng(random_device{}());//随机函数
for(int i=1;i<=d;i++)
val[i]=rng();//给d位字符串每位给个权值
cnt.clear();
ans=0;
ull base=13131;
for(int i=1;i<=d;i++){
for(int u=1;u<=n;u++){
ha[u]=0;
t[i][u].clear();
}
}
for(int i=1;i<=d;i++){
for(int u=1;u<=n;u++){
rt[i][u]=u;//d个图的所有点父节点是自己
//ha[u]+=u*val[i];//定义每个点的哈希值是d个图里本身所在连通块的父节点的值乘那个图的权值之和
//字符串哈希是si*base^i,k个长度就是k个数的权值和
t[i][u].push_back(u);//每个图的每个祖先所管的连通块所有节点
}
}
fac[0]=1;
for(int i=1;i<=d;i++){
fac[i]=fac[i-1]*base;
for(int j=1;j<=n;j++){
ha[j]=ha[j]+j*fac[i];
}
}
}
void add(ull ha){
int x=cnt[ha];
ans+=x;//考虑新添一个点可以匹配x个点,因为是无序对,
++cnt[ha];//所以不乘2,不包括(y,y),所以不加1
}
void del(ull ha){
int x=cnt[ha];
ans-=x-1;//是从(x-1)状态转移过来的,要减去x-1时的贡献
if(--cnt[ha]==0)
cnt.erase(ha);//可以加快速度
}
void solve(){
n=read(),m=read(),d=read(),q=read();//用快读的时候所有读入都要快读
d++;//实际有d+1个图
init();
for(int u=1;u<=n;u++)
add(ha[u]);
for(int i=1;i<=m;i++){
int u,v;
u=read(),v=read();
for(int j=1;j<=d;j++){
int k=j;
u=rt[k][u],v=rt[k][v];
if(u==v){
break;
}
if(t[k][v].size()>t[k][u].size())
swap(u,v);
for(int x:t[k][v]){
t[k][u].push_back(x);
rt[k][x]=u;
del(ha[x]);
//ha[x]+=(u-v)*val[k];
ha[x]+=(u-v)*fac[k];
add(ha[x]);
}
t[k][v].clear();
}
}
//cout<<ans<<"..."<<endl;
while(q--){
int u,v,k;
u=read(),v=read(),k=read();
u=rt[k][u],v=rt[k][v];
if(u==v){
cout<<ans<<endl;
continue;
}
if(t[k][v].size()>t[k][u].size())
swap(u,v);
for(int x:t[k][v]){
t[k][u].push_back(x);
rt[k][x]=u;
del(ha[x]);
//ha[x]+=(u-v)*val[k];
ha[x]+=(u-v)*fac[k];
add(ha[x]);
}
t[k][v].clear();
cout<<ans<<endl;
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
t=read();
while(t--){
solve();
}
return 0;
}
另一题代码:
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
typedef long long ll;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int N=5e3+10;
const int mod=1e9+7;
#define fi first
#define se second
#define ls u<<1
#define rs u<<1|1
const int D=205;
inline int read(){
int res = 0, ch, flag = 0;
ch = getchar();
if(ch==EOF)
exit(0);
if(ch == '-') //判断正负
flag = 1;
else if(ch >= '0' && ch <= '9') //得到完整的数
res = ch - '0';
while((ch = getchar()) >= '0' && ch <= '9' )
res = res * 10 + ch - '0';
return flag ? -res : res;
}
int rt[D][N];
vector<int> t[D][N];
int ha[N],val[D];
int ans,d,n,m;
unordered_map<int,int> cnt;
void add(int ha){
int x=cnt[ha];
ans+=2*x+1;
++cnt[ha];
}
void del(int ha){
int x=cnt[ha];
ans-=2*x-1;
if(--cnt[ha]==0)
cnt.erase(ha);
}
void solve(){
int d,n,m;
d=read(),n=read(),m=read();
mt19937_64 rng(random_device{}());
for(int i=1;i<=d;i++)
val[i]=rng();
for(int i=1;i<=d;i++){
for(int u=1;u<=n;u++){
rt[i][u]=u;
ha[u]+=u*val[i];
t[i][u].push_back(u);
}
}
for(int u=1;u<=n;u++)
add(ha[u]);
while(m--){
int u,v,k;
//cin>>u>>v>>k;
u=read(),v=read(),k=read();
u=rt[k][u],v=rt[k][v];
if(u==v){
cout<<ans<<endl;
continue;
}
if(t[k][v].size()>t[k][u].size())
swap(u,v);
for(int x:t[k][v]){
t[k][u].push_back(x);
rt[k][x]=u;
del(ha[x]);
ha[x]+=(u-v)*val[k];
add(ha[x]);
}
t[k][v].clear();
cout<<ans<<endl;
}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}