1.题目:[Noi2015]程序自动分析(STL+并查集)
题解:
先把该连的连起来,然后在判断不该相等的相不相等。但是这样有个问题(不然就是普及-了喂)i和j的范围1e9,但是n只有1e6,我们知道总点数最多只有2*n个,所以这个时候需要离散啦,这里运用了一些STL
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
struct ask{int x,y,e,p1,p2;}que[1000005];
int fa[2000005],a[2000005];
int find(int x)
{
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
int n,i,cnt=0;
bool fff=false;
scanf("%d",&n);
for (i=1;i<=n*2;i++) fa[i]=i;
for (i=1;i<=n;i++)
{
scanf("%d%d%d",&que[i].x,&que[i].y,&que[i].e);
a[++cnt]=que[i].x; a[++cnt]=que[i].y;
}
sort(a+1,a+cnt+1);
int tot=unique(a+1,a+cnt+1)-a-1;
for (i=1;i<=n;i++)
{
que[i].p1=upper_bound(a+1,a+cnt+1,que[i].x)-a-1;//在a这个升序数组中查找 最小的 比que[i].x这个数大的位置,起到离散化的作用
que[i].p2=upper_bound(a+1,a+cnt+1,que[i].y)-a-1;
}
for (i=1;i<=n;i++)
if (que[i].e)
{
int a=find(que[i].p1),b=find(que[i].p2);
if (a!=b) fa[a]=b;
}
for (i=1;i<=n;i++)
if (!que[i].e && find(que[i].p1)==find(que[i].p2))
{
printf("NO\n"); fff=true; break;
}
if (fff) continue;
printf("YES\n");
}
}
2.题目:Cube Stacking
题意:有n个立方体,初始时每个立方体都在独立的一个栈里,支持两个操作:
1.Move a b 把包含立方体a的栈整体移动到包含立方体b的栈顶 2.Count a,询问立方体a下方有多少个立方体。N<=30000
题解:这也是一道很有意思的题目了
主要就是三个数组的设置
f[k]立方体k所属栈的栈底元素;cnt[k]为立方体k到所属栈底的元素个数;num[k]为k所属栈的总数量
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#define N 30005
using namespace std;
int f[N],cnt[N],num[N];
//f[k]立方体k所属栈的栈底元素;cnt[k]为立方体k到所属栈底的元素个数;
//num[k]为k所属栈的总数量
int find(int x)
{
if (f[x]!=x)
{
int fa=f[x];
f[x]=find(fa);
cnt[x]+=cnt[fa];
}
return f[x];
}
void merge(int x,int y)
{
int a=find(x),b=find(y);
f[a]=b;
cnt[a]=num[b];
num[b]+=num[a];
}
int main()
{
int n,i;
for (i=1;i<=30000;i++)
f[i]=i,num[i]=1;
scanf("%d",&n);
while (n--)
{
char c;int x,y;
while (c!='M' && c!='C') c=getchar();
if (c=='M')
{
scanf("%d%d",&x,&y);
merge(x,y);
}
else
{
scanf("%d",&x);
int ff=find(x);
printf("%d\n",cnt[x]);
}
if (n) c=getchar();
}
}
3.题目:食物链
题解:
我们可以开3*n的数组,设x为本体,x+n为食物,x+2*n是天敌,对于1操作来说,x不能是y的食物,也不能是天敌,同理y也是;对于2操作来说,x不能是y的同类和食物。
代码:
#include <cstdio>
#define N 150005
using namespace std;
int fa[N];
int find(int x)
{
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void uni(int x,int y)
{
int a=find(x),b=find(y);
fa[a]=b;
}
int main()
{
int n,k,i;
scanf("%d%d",&n,&k);
int ff=0;
for (i=1;i<=n*3;i++) fa[i]=i;
for (i=1;i<=k;i++)
{
int id,x,y;
scanf("%d%d%d",&id,&x,&y);
if ((x==y && id==2) || x>n || y>n){ff++; continue;}
if (id==1)
{
if (find(x)==find(y+n) || find(x)==find(y+2*n) || find(x+n)==find(y) || find(x+n*2)==find(y)) {ff++; continue;}
uni(x,y);
uni(x+n,y+n);
uni(x+2*n,y+2*n);
}
else
{
if (find(x)==find(y) || find(x)==find(y+n) || find(x+2*n)==find(y)) {ff++; continue;}
uni(x,y+2*n);
uni(x+n,y);
uni(x+2*n,y+n);
}
}
printf("%d",ff);
}
4.题目: Find them, Catch them
题解:同食物链
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#define N 400005
using namespace std;
int fa[N];
int find(int x)
{
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
int n,m,i,x,y;
scanf("%d%d",&n,&m);
for (i=1;i<=n*2;i++) fa[i]=i;
for (i=1;i<=m;i++)
{
char ch;ch=getchar();
while (ch!='A' && ch!='D') ch=getchar();
scanf("%d%d",&x,&y);
int a=find(x),b=find(y);
if (ch=='A')
{
if (a!=b && find(x+n)!=b && find(y+n)!=a) printf("Not sure yet.\n");
else if (a!=b) printf("In different gangs.\n");
else printf("In the same gang.\n");
}
else
{
int c=find(x+n),d=find(y+n);
fa[c]=b;
fa[d]=a;
}
}
}
}
5.题目:关押罪犯
题解:
将边从大到小排序,把这些边连的两个点分到两个集合中,然后运用+n啥的合并,如果搜到一条边发现x和y已经属于一个集合,那无法避免了,因为只有迁就了这条边,更大的边才不会被当做答案。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
struct hh
{
int x,y,z;
}a[100005];
int fa[40005];
int cmp(hh a,hh b){return a.z>b.z;}
int find(int x)
{
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
int n,i,m;
scanf("%d%d",&n,&m);
for (i=1;i<=m;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
for (i=1;i<=n*2;i++)
fa[i]=i;
int ll=n-1;
sort(a+1,a+m+1,cmp);
for (i=1;i<=m;i++)
{
int x1=find(a[i].x),x2=find(a[i].y);
if (x1==x2)
{
printf("%d",a[i].z);
return 0;
}
fa[x2]=find(a[i].x+n);
fa[x1]=find(a[i].y+n);
}
printf("0");
}
总结:
并查集常见题目:
1. kruskal最小生成树
2. 食物链,团伙:每个格子占2-3个块,x+n,y+n代表敌人,x+2n,y+2n代表食物
经典代码
for (i=1;i<=n*3;i++)
fa[i]=i;
if (find(a)==find(b+n)||find(b)==find(a+n)||find(a+2*n)==find(b)||find(a)==find(b+n*2))
{
ans++;continue;
}
uni(a,b);
uni(a+n,b+n);
uni(a+2*n,b+2*n);
3. 银河英雄+摞盒子:before[]存x之前的个数,cnt[]存这一列的个数
经典代码
if (fa[x]!=x)
{
int fat=fa[x];
fa[x]=find(fa[x]);
before[x]+=before[fat];
}
return fa[x];
if (x1!=x2)
{
fa[x1]=x2;
before[x1]+=cnt[x2];
cnt[x2]+=cnt[x1];
}
4. 打击犯罪:倒悬并查集,倒着开始连,每一个连完后就判断一下有几个father
5.这个农场一共有被用M条双向道路连接的N个谷仓(1<=N,M<=200000)。为了关闭整个农场,FJ 计划每一次关闭掉一个谷仓。当一个谷仓被关闭了,所有的连接到这个谷仓的道路都会被关闭,而且再也不能够被使用。
FJ现在正感兴趣于知道在每一个时间(这里的“时间”指在每一次关闭谷仓之后的时间)时他的农场是否是“全连通的”——也就是说从任意的一个开着的谷仓开始,能够到达另外的一个谷仓。注意自从某一个时间之后,可能整个农场都开始不会是“全连通的”。
【输入格式】
输入的第一行是N和M。下面的M行每行都描述了一条连接两个谷仓的双向路径的两个端点(输入的点保证在1...N的范围内),最后的N行是一个1...N的排列,描述每0个谷仓被关闭的顺序。
【输出顺序】
输出一共有N行,每行可以是“YES”或者“NO”。第一行表示一开始时整个农场是否是“全连通的”,然后第i+1行表示在第i次的关闭谷仓之后整个农场是否是“全连通的”。
【样例输入】
4 3
1 2
2 3
3 4
3
4
1
2
【样例输出】
YES
NO
YES
YES
100%数据(1<=N,M<=200000)
倒悬并查集,由于不用知道每个联通块具体的个数,只需要统计联通块的个数,设tt,连一次少一块嘛
经典代码:
for (i=1;i<=n;i++) fa[i]=i;
int tt=n;
for(i=n;i>=1;i--)
{
d[i]=1;
c[a[i]]=true;
for (j=front[a[i]];j;j=edge[j].next)
if(c[edge[j].to])
{
intx1=find(edge[j].to),x2=find(a[i]);
if(x1!=x2)
{
fa[x2]=x1;tt--;
}
}
if (tt!=i) d[i]=0;
}