目录
并查集模板
首先是初始化,然后是查找和合并操作。
void init() {
for(int i=1;i<=n;i++)
fa[i] = i;
}
int get(int x) {
if(x==fa[x]) return x;
return fa[x] = get(fa[x]);
}
void merge(int x,int y) {
fa[get(x)] = get(y);
}
poj1456
题目链接
http://poj.org/problem?id=1456
题目
有n个商品,每个商品有利润p和过期时间d,每天只能卖一个商品。过期的商品不能再卖,问如何安排每天卖的商品。
题解
上次用 优先队列+贪心 解决了这一道问题:https://blog.csdn.net/zxwsbg/article/details/82717757
这次换一种贪心思路,我们直接按照每个商品的利润从大到小排序,建立一个关于d的并查集。
如果在当前商品在d天后过期,就找从d往前数第一个空闲的位置( fa[d] ), 如果fa[d]>0,那么就合并(fa[d],fa[d]-1),这样如果下一次又有一个点在d天后过期,它找到的就是fa[fa[d]-1]了。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 20005;
const int N = 105;
inline void read(int &x) {
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,fa[maxn];
struct node{
int p,d;
bool operator<(const node&b)const{
return p>b.p;
}
}a[maxn];
void init() {
for(int i=0;i<=10000;i++)
fa[i]=i;
}
int get(int x) {
if(x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
void merge(int x,int y) {
fa[get(x)] = get(y);
}
int main()
{
while(cin>>n) {
ll ans = 0;
for(int i=1;i<=n;i++) read(a[i].p),read(a[i].d);
sort(a+1,a+n+1);
init();
for(int i=1;i<=n;i++) {
int temp = get(a[i].d);
if(temp>0) {
ans += a[i].p;
merge(temp,temp-1);
}
}
cout<<ans<<endl;
}
return 0;
}
边带权并查集例题1
题目链接
题意
一个划分为n列的星际战场,各列编号为1,2,3……n。有n艘战舰,也依次编号为1,2,3……n,其中第i号战舰处于第i列。
有m条指令,每个指令为下列两种之一:
- M i j ,将第i列的战舰保持原有顺序,接到第j列尾部;
- C i j ,询问第i列和第j列战船是否在同一列中,如果是,输出它们之间隔了多少战船,否则输出-1。
题解
在原有的基础上,我们新建一个数组d,d[x]记录战舰 x 与 fa[x] 之间的边的权值。在路径压缩把x直接指向它的祖先时,把d[x]更新为从x到它祖先路径上所有边的权值的和。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;
inline void read(int &x) {
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int t,d[maxn]; //d数组记录的是从d[x]到d[fa[x]]的距离
string s;
int a[maxn],fa[maxn],size[maxn];
void init() {
for(int i=0;i<=30005;i++) fa[i] = i, size[i] = 1; //size记录的是该根节点下有多少个子节点
}
int get(int x) {
if(x==fa[x]) return x;
int root = get(fa[x]); //此时fa[fa[x]]已经维护好了,root记录的就是这个值
d[x] += d[fa[x]]; //把x叠加上去
return fa[x] = root; //再把x的爸爸记录成fa[fa[x]]
}
void merge(int x,int y) {
x = get(x), y = get(y);
fa[x] = y;
d[x] = size[y];
size[y] += size[x];
}
int query(int l,int r) {
if(get(l)!=get(r)) return -1;
return abs(d[r]-d[l])-1;
}
int main()
{
cin >> t;
init();
while(t--) {
cin>>s;
int l,r;
read(l),read(r);
if(s=="M") merge(l,r);
else cout<<query(l,r)<<endl;
}
return 0;
}
边带权并查集例题2
题目链接
http://poj.org/problem?id=1733
题目
有一个长度已知的01串,给出[l,r]这个区间中的1是奇数个还是偶数个,给出一系列语句,问前几个是正确的。
题解
首先,s[l~r]有偶数个1,表示sum[l-1]和sum[r]的奇偶性相同,奇数个1同理。
然后我们观察数据范围,可以先把所有的询问离散化。
边权d[x]=0,表示x与fa[x]的奇偶性相同,反之不同。
- 如果x和y在同一个根节点下,那么在get(x),get(y)执行完成后,x和y的奇偶性关系就是d[x]^d[y],如果这个值和ans不同,就找到了违反规则的点了;
- 如果两个不在一个根节点下,那么就寻找两个的树根,记为p和q,然后将p接到q上去:fa[p] = q;然后x到y的路径就变成了x->p->q->y,ans=d[x]^d[y]^d[p]。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;
inline void read(int &x) {
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,m,a[maxn*10],fa[maxn*10],d[maxn];
struct node{
int l,r,ans;
}q[maxn];
void discrete() {
int t = 0;
for(int i=0;i<m;i++) {
a[t++] = q[i].l-1;
a[t++] = q[i].r;
}
sort(a,a+t);
n = unique(a,a+t)-a;
}
void init() {
for(int i=0;i<=2*n;i++) fa[i] = i;
}
int get(int x) {
if(x==fa[x]) return x;
int root = get(fa[x]);
d[x] ^= d[fa[x]];
return fa[x] = root;
}
void merge(int x,int y) {
fa[get(x)] = get(y);
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++) {
read(q[i].l),read(q[i].r);
string s;
cin>>s;
if(s=="even") q[i].ans = 0;
else q[i].ans = 1;
}
discrete();
init();
for(int i=0;i<m;i++) {
int x = lower_bound(a,a+n,q[i].l-1)-a;
int y = lower_bound(a,a+n,q[i].r)-a;
int fa_x = get(x), fa_y = get(y);
if(fa_x==fa_y) {
if(d[x]^d[y]!=q[i].ans) {
cout<<i<<endl;
return 0;
}
}else {
fa[fa_x] = fa_y;
d[fa_x] = d[x] ^ d[y] ^ q[i].ans;
}
}
cout<<m<<endl;
return 0;
}
扩展域并查集例1
题目链接
http://poj.org/problem?id=1733
题目
有一个长度已知的01串,给出[l,r]这个区间中的1是奇数个还是偶数个,给出一系列语句,问前几个是正确的。
题解
刚才用了“边带权”的并查集,这一次用扩展域并查集。
将x分为两个节点x_odd和x_even,y同理,如果x和y奇偶性相同的话,则合并x_odd与y_odd,x_even和y_even,依次类推。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;
inline void read(int &x) {
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,m,a[maxn*10],fa[maxn*10];
struct node{
int l,r,ans;
}q[maxn];
void discrete() {
int t = 0;
for(int i=0;i<m;i++) {
a[t++] = q[i].l-1;
a[t++] = q[i].r;
}
sort(a,a+t);
n = unique(a,a+t)-a;
}
void init() {
for(int i=0;i<=2*n;i++) fa[i] = i;
}
int get(int x) {
if(x==fa[x]) return x;
return fa[x] = get(fa[x]);
}
void merge(int x,int y) {
fa[get(x)] = get(y);
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++) {
read(q[i].l),read(q[i].r);
string s;
cin>>s;
if(s=="even") q[i].ans = 0;
else q[i].ans = 1;
}
discrete();
init();
for(int i=0;i<m;i++) {
int x = lower_bound(a,a+n,q[i].l-1)-a;
int y = lower_bound(a,a+n,q[i].r)-a;
int x_odd = x, x_even = x+n;
int y_odd = y, y_even = y+n;
if(!q[i].ans) {
if(get(x_odd)==get(y_even)) {
cout<<i<<endl;
return 0;
}
merge(x_odd,y_odd);
merge(x_even,y_even);
}else {
if(get(x_odd)==get(y_odd)) {
cout<<i<<endl;
return 0;
}
merge(x_odd,y_even);
merge(x_even,y_odd);
}
}
cout<<m<<endl;
return 0;
}
扩展域并查集例2
题目链接
http://poj.org/problem?id=1182
题目
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是”1 X Y”,表示X和Y是同类。
第二种说法是”2 X Y”,表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
题解
把每个动物拆分成三个节点:x_self,x_enemy,x_eat,如果x与y是同类的话,就合并x_eat和y_self,x_self和y_eat;x吃y也是同理。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
#define INIT(x) memset(x,0,sizeof(x))
#define eps 1e-8
#define next next_
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x7fffffff;
const int inf = 0x3f3f3f3f;
const int maxn = 200005;
const int N = 105;
inline void read(int &x) {
int f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
int n,m,a[maxn*10],fa[maxn*10];
struct node{
int l,r,ans;
}q[maxn];
void discrete() {
int t = 0;
for(int i=0;i<m;i++) {
a[t++] = q[i].l-1;
a[t++] = q[i].r;
}
sort(a,a+t);
n = unique(a,a+t)-a;
}
void init() {
for(int i=0;i<=2*n;i++) fa[i] = i;
}
int get(int x) {
if(x==fa[x]) return x;
return fa[x] = get(fa[x]);
}
void merge(int x,int y) {
fa[get(x)] = get(y);
}
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++) {
read(q[i].l),read(q[i].r);
string s;
cin>>s;
if(s=="even") q[i].ans = 0;
else q[i].ans = 1;
}
discrete();
init();
for(int i=0;i<m;i++) {
int x = lower_bound(a,a+n,q[i].l-1)-a;
int y = lower_bound(a,a+n,q[i].r)-a;
int x_odd = x, x_even = x+n;
int y_odd = y, y_even = y+n;
if(!q[i].ans) {
if(get(x_odd)==get(y_even)) {
cout<<i<<endl;
return 0;
}
merge(x_odd,y_odd);
merge(x_even,y_even);
}else {
if(get(x_odd)==get(y_odd)) {
cout<<i<<endl;
return 0;
}
merge(x_odd,y_even);
merge(x_even,y_odd);
}
}
cout<<m<<endl;
return 0;
}