说!你跟他到底有没有关系?
好题题单
并查集
并查集的本质是对传递性关系的维护
普通并查集
初始化
1 2 3
| void init(){ for(ll i=1;i<maxn;i++) fa[i]=i; }
|
路径压缩+查找
1 2 3
| ll found(ll x){ return fa[x]==x? x:fa[x]=found(fa[x]); }
|
合并
1 2
| ll fu=found(u),fv=found(v); fa[fu]=fv;
|
基础题:
小希的迷宫
Wireless Network
带权并查集
做这类带权并查集的题目,解决并查集中两两的关系时,可以用向量的思想来做。只要保证路径压缩时,a+b=c,那么这道题就可以放心大胆的用向量加减法来处理权值了。
关于权值di[i]的初始化
对于权值di[i],是需要初始化的,不过一般都是初始化为0,在定义其为全局变量时,就默认为0了,然后有时候就没写在代码里,但是要初始化的。
经典题型1
食物链
题意 a吃b,b吃c,c吃a,求假话数量
思路:带权并查集,比如一句话包含a和b,判断a,b是否在同一并查集内,不在则合并,在则判断这句话是否正确即可。关键在于权值的设置,
这个权值的设置十分巧妙,有向量那味,但我还是不知道为什么能这样
感觉这个权值的设置是个巧合,刚好能这么做。
踩坑:并查集合并一定是fa[fy]=fx,不能fa[y]=x;
1 2 3 4 5 6 7 8 9 10
| ll found(ll x){ if(fa[x]==x){ return x; } else{ ll fx=found(fa[x]); di[x]=(di[x]+di[fa[x]])%3; return fa[x]=fx; } }
|
经典题型2
How Many Answers Are Wrong
TT 写一串数字,对 FF 不可见
FF 选择一个区间(会重复操作), TT 把这个区间的和告诉 FF,然额,一些和是不正确的(好狠心的女生),所以,有一些答案是矛盾的,根据这些矛盾求出答案错误的个数。
注意两点:1。TT 给的一个 和 是正确的,如果它与之前给的 和 不矛盾。
2。FF 发现一个与之前矛盾的 和 之后,该 和 不再参与之后的分析,直接被抛弃了
思路 带权并查集,注意区间端点的设置,为了衔接,将[3,4]和[5,6]变为(2,4]。和(4,6],这样子两边端点才能衔接上,才会在一个并查集里。
基础题:
Navigation Nightmare
灵活运用(带权并查集+hash)
本题解决:
1.hash后对并查集是否有影响
2.并查集中子父结点的大小关系对结果的影响
Parity game
题意:A有一段最大长度为1e9只含0和1的序列,B询问m次,m<5e3
每次询问[l,r]之间1的个数,A会告诉他是奇数个还是偶数个
但是A的回答可能是错误的,请判断A的回答从哪次开始矛盾
思路:
令a为子节点,b为父节点
用权值1表示a到b含奇数个1,用权值0表示a到b含偶数个1
虽然A的长度很长,有1e9,但B只询问m次,m<5e3,每次询问最多出现两个数字,所以最多1e4种数字
所以我们可以将数字长度离散化,我这里是采用hash的方式,这里不用担心hash后会对程序有影响
因为这里hash只是将原数字映射到另一个数字罢了,hash(x)与x就为同一个数字,完全不会有影响**,
这里进一步说明,hash(x)对程序是没影响的。
本来输入是x和y,是x到y,x是小于y的,路径压缩时默认子节点是小于父结点的,
**但存在一个问题,hash(x)可能会大于hash(y),这样子会不会有影响呢?
经检验,这里x,y两者大小关系,对运算无影响。因为如果是x>y,那么我们将其反过来,就相当于-w了。
那么我们的运算结果还是一样的,即0+1=0-1 0+0=0-0 1+0=1-0 1+1=1-1(每次运算记得模2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include<stdio.h> #include<string.h> #pragma comment(linker, "/STACK:102400000,102400000") #define Debug(x) cout<<#x<<':'<<x<<endl typedef long long ll; #define INF 0x7fffffff const ll maxn=1e5+11;
#define jump 10007 #define mod 100003 ll lib[mod + 2]; inline ll hash1(ll x){ ll num = x; x = (x % mod + mod) % mod; while (lib[x] != -1 && lib[x] != num) x = (x + jump) % mod; if (lib[x] == -1) { lib[x] = num; } return x; }
ll fa[maxn],di[maxn]; ll found(ll x){ if(fa[x]==x){ return x; } else{ ll fx=found(fa[x]); di[x]=(di[x]+di[fa[x]])%2; return fa[x]=fx; } } void init(){ memset(lib,-1,sizeof(lib)); for(ll i=0;i<maxn;i++) fa[i]=i; } int main(){ init(); ll n,q; scanf("%lld%lld",&n,&q); ll ans=q,flag=0; for(ll i=1;i<=q;i++){ ll u,v,w; char ar[11]; scanf("%lld %lld %s",&u,&v,ar); if(flag==1) continue; if(ar[0]=='e') w=0; else w=1; u-=1; u=hash1(u),v=hash1(v); ll fu=found(u),fv=found(v); if(fu==fv){ if((w+di[v])%2==di[u]) ; else{ flag=1; ans=i-1; } } else{ fa[fv]=fu; di[fv]=(w+di[u]-di[v]+2)%2; } } printf("%lld\n",ans); return 0; }
|
灵活运用(贪心+并查集)
Supermarket
这里其实用贪心做,并查集只是用来作为工具,使得速度更加快。
这道题解决: 用并查集以O(1)找到最近的非空点
题意是买卖N件东西,每件东西都有个截止时间,在截止时间之前买都可以,而每个单位时间只能买一件。问最大获利。
思路如果购买不冲突,那么全部买下来就可以了。存在冲突,就需要取舍。显然在冲突的时候我们选择价格高的更优,如此就可以用贪心的算法。先将物品按照价格从高到底的顺序排列,购买一个就在时间点上做一个标记,只要不冲突就可以购买。 如何快速找到第一个不冲突的时间点呢,个人感觉使用并查集很好得解决了这个问题。用并查集维护当前节点最近的空闲点即可。每当一个位置i被选中就将当前节点的父节点更新为i-1,查询时只要父节点非零就证明有空闲位置,选中更新输出答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
#include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> using namespace std; const int MAXN=10010; int F[MAXN]; struct Node { int p,d; }node[MAXN]; bool cmp(Node a,Node b) { return a.p>b.p; } int find(int x) { if(F[x]==-1)return x; return F[x]=find(F[x]); } int main() { int n; while(scanf("%d",&n)==1) { memset(F,-1,sizeof(F)); for(int i=0;i<n;i++) scanf("%d%d",&node[i].p,&node[i].d); sort(node,node+n,cmp); int ans=0; for(int i=0;i<n;i++) { int t=find(node[i].d); if(t>0) { ans+=node[i].p; F[t]=t-1; } } printf("%d\n",ans); } return 0; }
|
灵活运用(连通块,并查集)
path queries
题意 有m次询问,每次询问一个数字q,问以u,v为端点的路径的最大值不超过q的u,v对有多少个
思路:考试时想了很久,由于没有考虑过连通块,之前也没碰到过。。
导致一直做不出来,就是感觉能做,但一直没做出来
首先我们要知道,对于两个不相连的连通块,如果将他俩连起来,
是需要一条边将这两者连起来的,如果这条边是这两个连通块的所有边的最大值,那么以这条边为最大值的u,v就有size[a]*size[b]个。
那么我们将所有边按从小到大排序,这样子保证新加入的边是目前边的最大值,即两个连通块的最大值,连通块则用并查集处理。
贴一个极简风格的大佬代码,真的是绝绝子
原大佬代码链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #pragma GCC optimize("-Ofast","-funroll-all-loops") #include<bits/stdc++.h> #define int long long using namespace std; const int N=2e5+10; int n,m,res[N],ans,f[N],sz[N]; vector<pair<int,int> > v[N]; int find(int x){return x==f[x]?x:f[x]=find(f[x]);} signed main(){ cin>>n>>m; for(int i=1,a,b,c;i<n;i++) scanf("%lld %lld %lld",&a,&b,&c),v[c].push_back({a,b});
for(int i=1;i<=n;i++) f[i]=i,sz[i]=1; for(int i=1;i<=2e5;i++){ for(auto j:v[i]){ int x=find(j.first),y=find(j.second); ans+=sz[x]*sz[y]; f[x]=y,sz[y]+=sz[x]; } res[i]=ans; } for(int i=1,x;i<=m;i++) scanf("%lld",&x),printf("%lld ",res[x]); return 0; }
|
带权并查集好题
Jumping Monkey HDU - 7136
题意:一个n<1e5的树,每个点有个权值w,从u点能走到v点当且仅当v是uv这条路径上的最大权值,求每个点可以到达的点的个数。
点击查看思路
也可以用带权并查集维护这个过程,当按点权从小到大枚举结点u,并将u作为所有与u相连的(已经枚举过的)连通块的根时,
我们让fa[ fv ]=fu,d[ fv ]=1;并查集合并时则使d[ x ]=d[ x ]+d[ fa[ x ] ],和树的深度是一个意思。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #pragma comment(linker, "/STACK:102400000,102400000") using namespace std; #define Debug(x) cout<<#x<<':'<<x<<endl #define INF 0x7fffffff typedef long long ll; typedef pair<ll,ll> P; const ll maxn=2e5+11; ll head[maxn],to[maxn],nex[maxn],var[maxn],d[maxn],fa[maxn]; ll tmp,n; P po[maxn]; void add(ll u,ll v){ to[tmp]=v;nex[tmp]=head[u];head[u]=tmp++; } ll found(ll x){ if(fa[x]==x){ return x; } ll t=found(fa[x]); d[x]=d[x]+d[fa[x]]; return fa[x]=t; } int main(){ ll ca;scanf("%lld",&ca); while(ca--){ tmp=1; scanf("%lld",&n); for(ll i=1;i<=n;i++){ head[i]=0;fa[i]=i;d[i]=0; } for(ll i=1;i<=n-1;i++){ ll u,v;scanf("%lld%lld",&u,&v); add(u,v);add(v,u); } for(ll i=1;i<=n;i++){ scanf("%lld",&var[i]); po[i]={var[i],i}; } sort(po+1,po+1+n); for(ll i=1;i<=n;i++){ ll u=po[i].second,w=po[i].first; for(ll j=head[u];j;j=nex[j]){ ll v=to[j]; if(var[v]>w) continue; ll fu=found(u),fv=found(v); fa[fv]=fu;d[fv]=1; } } for(ll i=1;i<=n;i++){ found(i); printf("%lld\n",d[i]+1); } } return 0; }
|