BZOJ.4298.[ONTAK2015]Bajtocja(Hash 启发式合并)

题目链接

\(Description\)

给定d张无向图,每张图都有n个点。一开始,在任何一张图中都没有任何边。
接下来有m次操作,每次操作会给出a,b,k,意为在第k张图中的点a和点b之间添加一条无向边。
你需要在每次操作之后输出有序数对(a,b)的个数,满足1≤a,b≤n,且a点和b点在d张图中都连通。
d<=200,n<=5000,m<=1000000

\(Solution\)

我们需要知道的只是每对点之间是否连通,即在同一张图所属的连通块是否一样
于是我们对每个点在d张图中所属的连通块标号进行哈希,这个哈希要能快速删除一个标号 插入一个标号
如果有两个点哈希后的值相同,那么这两个点在d张图中都连通。于是我们再对这个哈希值做一遍哈希,来计算相同哈希值的个数
连边时用启发式合并,每次将size小的连通块全部修改fa,总复杂度O(dnlogn)

//75500kb   4628ms
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
typedef unsigned long long ull;
const int N=5005,D=205,M=1e6+5,mod=2e6;
const ull seed=769;

int n,m,d,H[D][N],Enum,to[M<<1],nxt[M<<1],fa[D][N],sz[D][N],Ans;
ull hs_id[N],Pow[D];
struct Hash_Table
{
    int top,h_H[mod+5],sk[mod],h_nxt[mod],cnt[mod];
    ull val[mod];
    void Init(){
        top=mod-5;
        for(int i=1; i<=top; ++i) sk[i]=i;
    }
    void Insert(ull x)
    {
        int p=x%mod;
        for(int i=h_H[p]; i; i=h_nxt[i])
            if(val[i]==x) {Ans+=2*cnt[i]+1,++cnt[i]; return;}
        ++Ans;//(a,a)也算一对
        int pos=sk[top--];
        val[pos]=x, cnt[pos]=1, h_nxt[pos]=h_H[p], h_H[p]=pos;
    }
    void Delete(ull x)
    {
        int p=x%mod,pre=h_H[p];
        if(val[pre]==x)
        {
            Ans-=2*cnt[pre]-1;
            if(!--cnt[pre]) sk[++top]=pre, h_H[p]=h_nxt[pre];
        }
        else
            for(int i=h_nxt[pre]; i; pre=i,i=h_nxt[i])
                if(val[i]==x)
                {
                    Ans-=2*cnt[i]-1;
                    if(!--cnt[i]) sk[++top]=i, h_nxt[pre]=h_nxt[i];
                    break;
                }
    }
}hs2;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AddEdge(int u,int v,int k){
    to[++Enum]=v, nxt[Enum]=H[k][u], H[k][u]=Enum;
}
void DFS(int x,int f,int k,int anc)
{
    hs2.Delete(hs_id[x]);
    hs_id[x]-=Pow[k]*fa[k][x];//fa就是bel了
    fa[k][x]=anc;
    hs_id[x]+=Pow[k]*anc;
    hs2.Insert(hs_id[x]);
    for(int i=H[k][x]; i; i=nxt[i])
        if(to[i]!=f) DFS(to[i],x,k,anc);
}
void Union(int u,int v,int k)
{
    if(fa[k][u]==fa[k][v]) return;
    if(sz[k][fa[k][u]]<sz[k][fa[k][v]]) std::swap(u,v);
    sz[k][fa[k][u]]+=sz[k][fa[k][v]];
    DFS(v,u,k,fa[k][u]);
    AddEdge(u,v,k),AddEdge(v,u,k);
}

int main()
{
    d=read(),n=read(),m=read();
    Pow[0]=1;
    for(int i=1; i<D; ++i) Pow[i]=Pow[i-1]*seed;
    hs2.Init();
    for(int i=1; i<=n; hs2.Insert(hs_id[i++]))
        for(int j=1; j<=d; ++j)
            fa[j][i]=i, sz[j][i]=1, hs_id[i]+=Pow[j]*i;//Hash = (∑s[i]seed^i) mod 2^{31}
    int a,b,k;
    while(m--)
        a=read(),b=read(),k=read(),Union(a,b,k),printf("%d\n",Ans);
    return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/8504020.html

时间: 2024-08-01 05:21:11

BZOJ.4298.[ONTAK2015]Bajtocja(Hash 启发式合并)的相关文章

BZOJ 2888 资源运输(启发式合并LCT)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2888 [题目大意] 不断加边,问每个连通块的重心到其它点的距离和的和 [题解] 启发式合并LCT,通过维护等差数列的首项和公差 来实现保存子树内所有节点到这个节点的距离之和. [代码] #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const in

BZOJ 2809 APIO2012 dispatching Treap+启发式合并 / 可并堆

题目大意:给定一棵树,选定一棵子树中的一些点,薪水和不能超过m,求点的数量*子树根节点的领导能力的最大值 考虑对于每个节点,我们维护一种数据结构,在其中贪心寻找薪金小的雇佣. 每个节点暴力重建一定不行,我们考虑可并数据结构,每个节点将子节点的信息直接合并即可 可以用启发式合并的Treap,也可以用可并堆 今天特意去学了这玩应0.0 先写了左偏树 然后又写了下随机堆-- 后者速度上更快一些 不过建议从左偏树开始学起 总之平衡树常数各种大就是了0.0 Treap+启发式合并 #include<cst

bzoj 1483: [HNOI2009]梦幻布丁 启发式合并vector

1483: [HNOI2009]梦幻布丁 Time Limit: 10 Sec  Memory Limit: 64 MB[Submit][Status][Discuss] Description N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色. Input 第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2...An表示第i个布丁的颜色从第三行起有M行,对于每个操

【BZOJ 3123】 [Sdoi2013]森林 主席树启发式合并

我们直接按父子关系建主席树,然后记录倍增方便以后求LCA,同时用并查集维护根节点,而且还要记录根节点对应的size,用来对其启发式合并,然后每当我们合并的时候我们都要暴力拆小的一部分重复以上部分,总时间复杂度为O(n*log),因为每个的节点只会作为小的部分合并,因此他所在的一小部分至少变大2倍,对于每一个节点他作为被合并方最多log次,因此其复杂度为O(n*log),而这个是绝对跑不满还差很多的,我们视他为无常数就好了,当然这一切都是建立在无拆分操作的基础之上,只要有了拆分启发式合并的复杂度就

BZOJ.3510.首都(LCT 启发式合并 树的重心)

题目链接 BZOJ 洛谷 详见这. 求所有点到某个点距离和最短,即求树的重心.考虑如何动态维护. 两棵子树合并后的重心一定在两棵树的重心之间那条链上,所以在合并的时候用启发式合并,每合并一个点检查sz[]大的那棵子树的重心(记为root)最大子树的sz[] * 2是否>n: 若>n,则向fa移动一次(先把合并点Splay到根).重心还一定是在sz[]大的那棵子树中,且移动次数不会超过sz[]小的子树的点数(所以总移动次数不会超过O(n)?). 复杂度 \(O(nlog^2n)\) 具体实现..

BZOJ 2733: [HNOI2012]永无乡(treap + 启发式合并 + 并查集)

不难...treap + 启发式合并 + 并查集 搞搞就行了 ---------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define rep(i, n) for(int i = 0; i &l

[BZOJ 1483][HNOI 2009]梦幻补丁(有序表启发式合并)

题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1483 分析: 先将不同的颜色的出现位置从小到大用几条链表串起来,然后统计一下答案 对于每次修改,修改一下答案即可,修改之后需要将两个颜色的链表合并就行了,但感觉似乎会TLE? 以下摘录与Hzwer的blog: 1:将两个队列合并,有若干队列,总长度为n,直接合并,最坏O(N), 2:启发式合并呢? 每次我们把短的合并到长的上面去,O(短的长度) 咋看之下没有多大区别, 下面让我们看看

BZOJ 1483 [HNOI2009]梦幻布丁 链式前向星+启发式合并

题意: 题意很清晰就是没有数据范围- -! 直到现在我都不知道数据范围有多大! 刚开始给定一个序列. 多次操作. 第一种是把值为x的点的值改为y 第二种是询问当前有多少段权值全部相同. 解析: 显然暴力合并的复杂度是O(nm)的,不可取. 所以来考虑黑科技. 如果我们改一改合并的方式. 每一次把短的接在长的上面,那么最少也是使短的长度变为原来的二倍. 所以这种操作如果不卡的话大概是log次? 但是要是卡的话好像挺难? 如果使合并次数增加那么每一次合并的元素就越来越少,所以应该是卡不住的. 所以我

BZOJ 2754 SCOI2012 喵星球上的点名 fail树+set启发式合并

题目大意:给定n个目标串和m个模式串,问这m个模式串每个在多少个目标串中出现过,以及n个目标串每个以最多多少个模式串为子串 我错了--就算用fail树+set启发式合并也优化不到O(nlog^2n)--这题的数据范围相当无解啊 首先将所有名字和点名的字符串全都插进AC自动机 将每个点上开一个set记录这个点是哪些喵星人的名字的前缀 然后建立fail树 沿着fail树从下到上启发式合并 每合并完一个点 如果这个点是某次点名的字符串 那么这次点名点到的喵星人就是这个点的set中的所有元素 统计答案即