把内容迁到这里来了,作为一部分补充
初始化:
for(int i = 1;i <= n; ++i)
f[i] = i;
路径压缩:
int find(int t)
{
if(f[t] == t) return t;
f[t] = find(f[t]);
return f[t];
}
合并:
f[find(x)] = find(y);
NOIP2010关押罪犯:构造虚点
假设x号罪犯与y号罪犯是仇人,x+n号罪犯与y+n号罪犯是仇人,那么可以推出:如果x与y在同一个监狱了,则x+n号犯人与y+n号也在同一个监狱里,一定会发生冲突
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node
{
int x,y,z;
}a[100010];
int n,m,f[50000];
bool cmp(node k,node p)
{
return k.z > p.z;
}
int find(int x)
{
if(f[x] == x) return x;
f[x] = find(f[x]);
return f[x];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n * 2; ++i) f[i] = i;
for(int i = 0;i < m; ++i) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
sort(a , a + m , cmp);
for(int i = 0;i < m; ++i)
{
if(find(a[i].x) == find(a[i].y))
{
printf("%d\n",a[i].z);
return 0;
}
f[find(a[i].x)] = find(a[i].y + n);
f[find(a[i].y)] = find(a[i].x + n);
}
printf("0\n");
return 0;
}
NOI2002银河英雄传说
在应用并查集的通识维护每列的总数以及每列中战舰到队列最前面的距离
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int f[30010],d[30010],last[30010];
int n,x,y;
char c;
int find(int x)
{
if(f[x] == x) return x;
int p = f[x];
f[x] = find(f[x]);
d[x] += d[p];
return f[x];
}
int main()
{
for(int i = 1;i <= 30000; ++i)
{
d[i] = 0;
f[i] = i;
last[i] = 1;
}
scanf("%d",&n);
for(int i = 0;i < n; ++i)
{
cin>>c;
scanf("%d%d",&x,&y);
//cout<<c<<endl;
if(c == 'M')
{
x = find(x);
y = find(y);
f[x] = y;
d[x] += last[y];
last[y] += last[x];
}
else
{
if(find(x) != find(y)) printf("-1\n");
else
{
printf("%d\n",abs(d[x] - d[y]) - 1);
// printf("%d %d",d[x],d[y]);
}
}
}
return 0;
}
hdu 3038
题目:给定一个序列(事先不知道序列的值),然后询问,从第a个数到第b个数的和是多少,如果与之前的情况相冲突,则ans++
带权并查集
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 200000 + 5;
int n, m;
int fa[maxn];
int sum[maxn];
int findz(int x)
{
if (x == fa[x]) return x;
int f = fa[x];
fa[x] = findz(fa[x]);
sum[x] += sum[f];
return fa[x];
}
/**
把左端点a和右端点b当成两个不同的量
求出sum[a],sum[b],如果他们属于同一个根(左端点相同),
只需要判断sum[a] + c 和sum[b] 是否相等即可
否则,无法找到矛盾,认为这个答案是合法的,进行更新
*/
bool unite(int x, int y, int c)
{
int a = findz(x), b = findz(y);
if (a == b)
{
if (sum[x] + c != sum[y]) return 0;
return 1;
}
else
{
fa[b] = a;
sum[b] = sum[x] - sum[y] + c;
return 1;
}
}
int main()
{
int a, b, c;
while(~scanf("%d%d", &n, &m))
{
int ans = 0;
/**
sum数组记录前缀和
*/
for(int i = 0; i <= n; i++) fa[i] = i,sum[i] = 0;
for(int i = 0; i < m; i++)
{
scanf("%d%d%d",&a,&b,&c);
if(!unite(a - 1, b, c)) ans++;
}
printf("%d\n",ans);
}
return 0;
}
食物链
这是一个十分经典的种类并查集 && 带权并查集 问题
解法一:
#include<cstdio>
using namespace std;
const int maxn = 1e5 + 10;
int f[maxn],val[maxn];
int n,m,ans,t,x,y;
int findz(int x)
{
if(f[x] == x) return x;
int tmp = findz(f[x]);
val[x] = (val[x] + val[f[x]]) % 3;
return f[x] = tmp;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n; ++i)
f[i] = i,val[i] = 0;
///val数组记录吃与被吃的关系 ,0表示与父亲同级,1表示吃父亲,2表示被父亲吃
ans = 0;
for(int i = 0;i < m;++i)
{
scanf("%d%d%d",&t,&x,&y);
if(x > n || y > n) ///第二个条件
{
ans++;
continue;
}
int a = findz(x),b = findz(y);
if(a == b) ///父亲相同
{
if(t == 1 && val[x] != val[y]) ans++;
if(t == 2 && (val[y] + 1) % 3 != val[x]) ans++;
/**
满足x吃y,在父亲相同的情况下
1) x吃父亲,y与父亲是同类 val[x]=1,val[y]=0
2) x被父亲吃,y吃父亲 val[x]=2,val[y]=1
3) x与父亲是同类,y被父亲吃 val[x]=0,val[y]=2
*/
}
else ///如果父亲不相同无法根据之前的条件判断x与y的关系
{
f[b] = a;
if(t == 1) val[b] = (val[x] - val[y] + 3) % 3;
/**
x与y是同类
1)b与a是同类,val[b] = 0--------> val[x] = val[y] (包含3种关系,不展开叙述了)
2)a吃b val[b] = 2 -----> (x与父亲a是同类,y吃父亲b val[x] = 0,val[y] = 1)
(x吃父亲a,y被父亲b吃 val[x] = 1,val[y] = 2)
(x被父亲a吃,y与父亲b是同类 val[x] = 2,val[y] = 0)
3)a被b吃 val[b] = 1 ----->(x与父亲a是同类,y被父亲b吃 val[x] = 0,val[y] = 2)
(x吃父亲a,y与父亲b是同类 val[x] = 1,val[y] = 0)
(x被父亲a吃,y吃父亲b val[x] = 2,val[y] = 1)
*/
else val[b] = (val[x] - val[y] + 2) % 3;
/**
x吃y
1)a吃b val[b] = 2--------> val[x] = val[y] (包含3种关系,不展开叙述了)
2)b与a是同类,val[b] = 0 -----> (x与父亲a是同类,y被父亲b吃 val[x] = 0,val[y] = 2)
(x吃父亲a,y与父亲b是同类 val[x] = 1,val[y] = 0)
(x被父亲a吃,y吃父亲b val[x] = 2,val[y] = 1)
3)a被b吃 val[b] = 1 -----> 略
*/
}
}
printf("%d\n",ans);
return 0;
}
poj 1417 背包+dp
一个关于说真话假话的问题orz
#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
#include<cstdio>
using namespace std;
vector<int>v[1000][2];
int f[1000],val[1000],dp[1000][1000];
int a[1000][2];
int n,x,y,p1,p2,tot;
string s;
int findz(int x)
{
if(f[x] == x) return x;
int tmp = findz(f[x]);
val[x] ^= val[f[x]];
return f[x] = tmp;
}
int main()
{
while(~scanf("%d%d%d",&n,&p1,&p2) && n + p1 + p2)
{
for(int i = 1;i <= p1 + p2; ++i)
{
f[i] = i,val[i] = 0;
v[i][1].clear();
v[i][0].clear();
a[i][0] = a[i][1] = 0;
}
for(int i = 1;i <= n; ++i)
{
scanf("%d%d",&x,&y);
cin>>s;
int k = (s == "no");
int ai = findz(x);
int bi = findz(y);
if(ai != bi)
{
f[ai] = bi;
val[ai] = val[x] ^ val[y] ^ k;
}
}///把他们分成多个集合,每个集合分为0/1,这里的0/1只是相对的,表示不同类
tot = 0;
for(int i = 1;i <= p1 + p2; ++i)
{
if(findz(i) == i)
{
tot++;
for(int j = 1;j <= p1 + p2; ++j)
if(findz(j) == i) v[tot][val[j]].push_back(j);
a[tot][1] = v[tot][1].size();
a[tot][0] = v[tot][0].size();
}
}///把每个集合里的人存起来
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
for(int i = 1; i <= tot; i++)
for(int j = p1; j >= 0;j--)
{
if(j >= a[i][0]) dp[i][j] += dp[i - 1][j - a[i][0]];
if(j >= a[i][1]) dp[i][j] += dp[i - 1][j - a[i][1]];
} ///从每个集合里取出0或者1进行dp,看能凑成p1个说真话的人的方案有多少种
if(dp[tot][p1] != 1)
{
printf("no\n");
continue;
}
priority_queue<int,vector<int>,greater<int> >q; ///打印分组方案
for(int i = tot;i >= 1;i--)
{
if(p1 >= a[i][0] && p2 >= a[i][1] && dp[i - 1][p1 - a[i][0]])
{
for(int j = 0; j < v[i][0].size();j++)
q.push(v[i][0][j]);
p1 -= a[i][0];
p2 -= a[i][1];
}
else if(p1 >= a[i][1] && p2 >= a[i][0] && dp[i - 1][p1 - a[i][1]])
{
for(int j = 0; j < v[i][1].size();j++)
q.push(v[i][1][j]);
p1 -= a[i][1];
p2 -= a[i][0];
}
}
while(!q.empty())
{
printf("%d\n",q.top());
q.pop();
}
printf("end\n");
}
return 0;
}
POJ 1984
带权并查集
把位置坐标分为x轴和y轴,所以有两个权值,x[i]记录i的x轴到其根节点x轴的距离,y[i]记录i的y轴到其根节点y轴的距离,
(压缩路径和合并集合时权值的改变类似于向量的加减)
最后把询问离线存储,再把询问按index排序,之后在询问之前先合并集合,接着再判断就好了
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 10;
struct node
{
int x,y,len;
char dir;
}a[maxn];
struct nod
{
int x,y,id,idx,ans;
}q[maxn];
struct nodee
{
int fa,x,y;
}f[maxn];
int n,m,k;
bool cmp1(nod k,nod l)
{
return k.id < l.id;
}
bool cmp2(nod k,nod l)
{
return k.idx < l.idx;
}
int findz(int x)
{
if(f[x].fa == x) return x;
int tmp = f[x].fa;
f[x].fa = findz(f[x].fa);
f[x].x += f[tmp].x;
f[x].y += f[tmp].y;
return f[x].fa;
}
void unionz(int x,int y,char dir,int len)
{
int fx = findz(x);
int fy = findz(y);
if(fx != fy)
{
f[fx].fa = fy;
if(dir == 'N')
f[fx].x = f[y].x - f[x].x + len,f[fx].y = f[y].y - f[x].y;
else if(dir == 'S')
f[fx].x = f[y].x - f[x].x - len,f[fx].y = f[y].y - f[x].y;
else if(dir == 'E')
f[fx].x = f[y].x - f[x].x,f[fx].y = f[y].y - f[x].y - len;
else f[fx].x = f[y].x - f[x].x,f[fx].y = f[y].y - f[x].y + len;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n; ++i)
f[i].fa = i,f[i].x = f[i].y = 0;
for(int i = 1;i <= m; ++i)
scanf("%d%d%d %c",&a[i].x,&a[i].y,&a[i].len,&a[i].dir);
scanf("%d",&k);
for(int i = 1;i <= k; ++i)
{
scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].id);
q[i].idx = i;
}
sort(q + 1,q + k + 1,cmp1);
int x = 0;
for(int i = 1;i <= k; ++i)
{
while(++x <= q[i].id) unionz(a[x].x,a[x].y,a[x].dir,a[x].len);
x--;
int fx = findz(q[i].x);
int fy = findz(q[i].y);
//cout<<fx<<' '<<fy<<endl;
if(fx != fy) q[i].ans = -1;
else q[i].ans = abs(f[q[i].x].x - f[q[i].y].x) + abs(f[q[i].x].y - f[q[i].y].y);
}
sort(q + 1,q + k + 1,cmp2);
for(int i = 1;i <= k; ++i) printf("%d\n",q[i].ans);
return 0;
}