一、食物链:A吃B,B吃C,C吃A,构成循环的关系。
思路:用1表示吃根节点的,2被根节点吃,3同类,于是有%3==1,吃根节点;%3==2被根节点吃
%3==0同类,这三种表示关系。即用每个点到根节点的距离来表明他们之间的关系。
#include<iostream>
using namespace std;
const int N=50005;
int fa[N],d[N];
int find(int x)
{
if(x!=fa[x])
{
int t=find(fa[x]);
d[x]+=d[fa[x]];//d每个点到根节点的距离
fa[x]=t;
}
return fa[x];
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
fa[i]=i;
int cnt=0;
while(k--)
{
int t,x,y;
scanf("%d%d%d",&t,&x,&y);
if(x>n||y>n) cnt++;
else
{
int px=find(x),py=find(y);
if(t==1)
{
if(px==py&&(d[x]-d[y])%3) cnt++;//在同一个集合下判断满不满足同类
else if(px!=py)
{
fa[px]=py;
d[px]=d[y]-d[x];//d[x]+d[px]=d[y]
}
}
else
{
if(px==py&&(d[x]-d[y]-1)%3)//x吃y,则x的等级比y大1,即x到根节点的距离比y小1
cnt++;
else if(px!=py)
{
fa[px]=py;
d[px]=d[y]-d[x]+1;//d[y]+1=d[x]+d[px]
}
}
}
}
printf("%d\n",cnt);
return 0;
}
1250. 格子游戏
思路:当形成环的时候就结束判断。怎么用并查集来判断有没有形成环呢?当两个点已经在同一个
集合里,如果再给这两个点连一条线就会形成环,所以判断给出的点是否已经在同一个集合里了就
可,即根结点是否一样。
#include<iostream>
using namespace std;
const int N=40010;
int fa[N];
int n,m;
int get(int x,int y)
{
return (x-1)*n+y;//将二维数组转换为一维存储
}
int find(int x)
{
if(x!=fa[x])
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n*n;i++)//转换为一维,有n*n个格子
fa[i]=i;
int res=0;
for(int i=1;i<=m;i++)
{
int x,y;
char ch;
cin>>x>>y>>ch;
int a,b;
a=get(x,y);
if(ch=='D')
b=get(x+1,y);
else
b=get(x,y+1);
int pa=find(a),pb=find(b);
if(pa==pb)//已经在同一个集合里的了,在次相连就会形成环
{
res=i;
break;
}
else
fa[pa]=pb;
}
if(!res)
cout<<"draw"<<endl;
else
cout<<res<<endl;
return 0;
}
搭配购买
思路:把要在一起搭配购买的连接起来在同一个集合,把这个集合的总价钱和总价值存在根节点,最后就成了0/1背包问题(因为对于每个集合要么就买下要么就不要)找出每个集合的根节点,有多少个集合就有多少个背包。
#include<iostream>
using namespace std;
const int N=5e4+10;
int fa[N],pr[N],val[N];
int f[N];
int find(int x)
{
if(x!=fa[x])
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
int n,m,w;
cin>>n>>m>>w;
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=n;i++)
cin>>pr[i]>>val[i];
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
int pa=find(a),pb=find(b);
if(pa!=pb)
{
fa[pa]=pb;
pr[pb]+=pr[pa];//更新整个集合的根节点的值,记录整个集合的总价值
val[pb]+=val[pa];
}
}
//0/1背包
for(int i=1;i<=n;i++)
{
if(fa[i]==i)
{
for(int j=w;j>=pr[i];j--)
f[j]=max(f[j],f[j-pr[i]]+val[i]);
}
}
cout<<f[w]<<endl;
return 0;
}
237. 程序自动分析
思路:先判断所有相等的情况,即把相等的放在同一个集合里,再判断不等的情况是否与前面判断出来的矛盾。这里需要把每组的条件进行离散化处理,因为最多只有n组,所以数据最多只有2*n个,所以用离散化把大范围的数据缩小来表示。
离散化:这道题用的是不保序的离散化方式,即不用保证离散化后每个值的相对位置保持不变,
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int N=2e5+10;//n组,可能有2*n个不同的数
int fa[N];
int n,t,cnt;
unordered_map<int,int> S;
struct type
{
int x,y,e;
}query[N];
int get(int x)//离散化,不保序
{
if(S.count(x)==0) S[x]=++cnt;
return S[x];
}
int find(int x)
{
if(x!=fa[x])
fa[x]=find(fa[x]);
return fa[x];
}
int main()
{
scanf("%d",&t);
while(t--)
{
S.clear();
cnt=0;
scanf("%d",&n);
int x,y,e;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&x,&y,&e);
query[i]={get(x),get(y),e};//将离散化后的x,y的值存入
}
for(int i=1;i<=cnt;i++)//最后映射成cnt个点
fa[i]=i;
//先处理相等的情况
for(int i=0;i<n;i++)
{
if(query[i].e==1)
{
int px=find(query[i].x),py=find(query[i].y);
fa[px]=py;
}
}
//判断不相等情况下的两个元素是否在同一集合中
int flag=0;
for(int i=0;i<n;i++)
{
if(query[i].e==0)
{
int px=find(query[i].x),py=find(query[i].y);
if(px==py)
{
flag=1;
break;
}
}
}
if(flag)
printf("NO\n");
else
printf("YES\n");
}
return 0;
}
239. 奇偶游戏(带边权并查集,维护与根节点的相对关系)
思路:先抽象化,利用前缀和的思想将[l,r]区间里1的个数的奇偶数用S[r]-S[L-1]来理解,当S[r]-S[l-1]为偶数时,说明S[r]与S[l-1]是同类的(同为奇或偶),用0来标识,否则是不同类的。于是可将每个数放入到同一个集合,通过维护每个点与根节点的关系来判断这两个点的关系。当d[x],d[y]与父节点d[fa]是同类时,如果d[x]+d[y]为偶,即d[x]^d[y]=0,没有说谎,否则在说谎。同理,当d[x],d[y]与父节点d[fa]是异类时,如果d[x]+d[y]为奇数,即d[x]^d[y]==1,说明没有在说谎。这道题妙处在于把区间抽象出两个点,用这两个点的类型来判别。
#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
const int N=1e4+10;
int fa[N],d[N];
int n,m;
unordered_map<int,int> S;
int get(int x)
{
if(S.count(x)==0) S[x]=++n;
return S[x];
}
int find(int x)
{
if(x!=fa[x])
{
int t=find(fa[x]);
d[x]^=d[fa[x]];//意义等同于d[x]+=d[fa[x]
fa[x]=t;
}
return fa[x];
}
int main()
{
cin>>n>>m;
int res=m;
n=0;
for(int i=0;i<N;i++)
fa[i]=i;
for(int i=0;i<m;i++)
{
int x,y;
string str;
cin>>x>>y>>str;
x=get(x-1),y=get(y);
int px=find(x),py=find(y);//Sy-Sx-1
int flag=0;//标记偶数,即Sy,Sx-1是同类
if(str=="odd") flag=1;//标记奇数,即Sy,Sx-1是不同类
if(px==py)
{
if(d[x]^d[y]!=flag)
{
res=i;
break;
}
}
else
{
fa[px]=py;
d[px]=d[x]^d[y]^flag;//(d[px]+d[x])^d[y]^flag
}
}
cout<<res<<endl;
return 0;
}
238. 银河英雄传说
思路:用一个size数组来维护每个集合的元素个数,同时d[x]来维护每个点到根节点的距离。于是
当一个结点x以结点y为父节点时i,d[x]=size[y]即x到根节y的距离等于以y为根结点的集合元素
个数,然后s[y]+=s[x];再查找根节点的时候要维护每个点到根节点的距离,这样每次再查找的
时候就可以保证每个点的d[x]一定是更新到了与根节点的距离。
#include<iostream>
using namespace std;
const int N=3e4+10;
int fa[N],d[N],s[N];
int find(int x)
{
if(x!=fa[x])//在查找的时候会自动更新每个点到根结点的距离
{
int t=find(fa[x]);
d[x]+=d[fa[x]];//更新每个点到根节点的距离
fa[x]=t;
}
return fa[x];
}
int main()
{
int t,m;
cin>>t;
for(int i=0;i<N;i++)
{
fa[i]=i;
s[i]=1;//没合并之前每个结点都是自成一个集合,每个集合的大小是1
}
while(t--)
{
char ch;
int x,y;
cin>>ch>>x>>y;
int px=find(x),py=find(y);
if(ch=='M')
{
fa[px]=py;
d[px]=s[py];//更新px到新的根节点的距离
s[py]+=s[px];
}
else
{
if(px!=py)
cout<<-1<<endl;
else
cout<<max(0,abs(d[x]-d[y])-1)<<endl;
}
}
return 0;
}