【NOIP模拟赛三】并查集+hash day2 third 雪后村庄(好题)

题目描述

 

输入

输出

输出q行,每行一个字符串“yes”或“no”(不包括引号)。

样例输入

(如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

2 4
3 4
1 2 3
2 3 2
2 4 4
1 2 3
1 3 2
2 3 2
3 4 4
4
1 3 3
1 3 2
1 4 3
3 4 4

样例输出

no
yes
no
no

提示


    其实我真正想写的是这道题啦……

    乍一看,求瓶颈边!只要判断每个村庄中a、b瓶颈边是否小于等于k就可以了,所以使用Kruskal树来做!

    但是询问和村庄数都很多,这使得q次就是q*n,无法跑过1s(废话)

    那么怎么办呢?注意到其实题目并不是让我们求瓶颈边,而是判断瓶颈边是否小于等于k,或者这样说:判断只使用小于等于k的边能否使a和b连通。

    我们考虑离线算法,将询问当成边一起做。使用并查集维护连通性,遇到询问时只需要判断每个村庄的a、b是否在同一并查集即可。但是这样仍然是q*n,会TLE(废话,要判断n次)。

    我们考虑利用启发式合并暴力合并并查集,让每个点直接指向根节点(其实就是集合标号),合并时让节点少的标号修改成节点多的,平均复杂度便是log(n*m)。用一个hash表记录每个节点在不同村庄的集合,判断时只需要判断a和b的hash值是否相等即可。最后优化一下常数、写一写读入优化神马的,可以跑进1s。

    代码如下:

#include<cmath> 
#include<cstdio> 
#include<cstring> 
#include<algorithm> 
using namespace std; 
#define mod 1000000007 
#define mod2 998244353 
#define N 200005 
#define M 500005 
int m,n,p,q,line[N]; 
int Set[N],sum[N];//属于哪个集合、集合中的元素个数 
int hash[N];//每个元素的哈希值 
int f[N],f2[N],fir[N],nex[N]; 
int ans[N]; 
void read(int &p) 
{ 
    p=0; 
    char c=getchar(); 
    while(c<'0'||c>'9')c=getchar(); 
    while(c>='0'&&c<='9')p=p*10+c-'0',c=getchar(); 
} 
struct node 
{ 
    int s,t,l,w; 
    bool operator<(node b)const
    { 
        return l==b.l?w<b.w:l>b.l; 
    } 
}A[M]; 
void upd(int a,int k)//将a号点标号变成k O(1) 
{ 
    int w=(a-1)%m+1,p=(a-1)/m; 
    hash[w]=((hash[w]+1ll*(k-Set[a])*f[p]%mod2)%mod2+mod2)%mod2; 
    //hash2[w]=((hash2[w]+1ll*(k-Set[a])*f2[p]%mod)%mod+mod)%mod; 
    Set[a]=k; 
} 
bool cmp(node a,node b) 
{ 
    if(!a.w) 
        return 0; 
    if(!b.w) 
        return 1; 
    return a.w<b.w; 
} 
int main() 
{
    read(n);read(m); 
    for(int i=1;i<=n;i++)//200000 
        read(line[i]); 
    f[0]=1; 
    for(int i=1;i<=n;i++)//200000 
        f[i]=1ll*f[i-1]*7%mod2; 
    for(int i=1;i<=n;i++)//200000 
    { 
        for(int j=1;j<=line[i];j++) 
        { 
            read(A[p+j].s);read(A[p+j].t);read(A[p+j].l); 
            A[p+j].s+=(i-1)*m; 
            A[p+j].t+=(i-1)*m; 
        } 
        p+=line[i]; 
    } 
    for(int i=1;i<=n*m;i++)//200000 
    { 
        upd(i,i); 
        sum[i]=1; 
        fir[i]=i; 
    } 
    read(q); 
    for(int i=1;i<=q;i++) 
    { 
        read(A[p+i].s);read(A[p+i].t);read(A[p+i].l); 
        A[p+i].w=i; 
    } 
    p+=q; 
    sort(A+1,A+p+1); 
    int x,y; 
    for(int i=1;i<=p;i++) 
    { 
        x=Set[A[i].s]; 
        y=Set[A[i].t]; 
        if(!A[i].w) 
        { 
            if(x!=y) 
            { 
                if(sum[x]>sum[y]) 
                    swap(x,y); 
                int j; 
                for(j=fir[x];;j=nex[j]) 
                { 
                    upd(j,y); 
                    if(!nex[j]) 
                        break; 
                } 
                nex[j]=fir[y]; 
                fir[y]=fir[x]; 
                sum[y]+=sum[x]; 
            } 
        } 
        else
            ans[A[i].w]=hash[A[i].s]==hash[A[i].t]; 
    } 
    for(int i=1;i<=q;i++) 
    { 
        if(ans[i]) 
            printf("yes\n"); 
        else
            printf("no\n"); 
    } 
} 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值