1 判断有向图是否为树
给出几条边,判断这个有向图是否为树。
从以下三点判断即可:
1.不存在环
2.不存在入度大于1的节点
3.入度为0的节点只有1个
前两点在find函数就可以直接判断,关于第二点一开始没看明白,画个图就能想通
#include<bits/stdc++.h>
using namespace std;
int fa[100001];
int vis[100001];
int pre[100001];
int istree=1;
int find(int x){
if(x==fa[x])return x;
else return fa[x]=find(fa[x]);
}
void init(){
for(int i=1;i<=100000;i++)
fa[i]=i;
memset(vis,0,sizeof(vis));
memset(pre,0,sizeof(pre));
istree=1;
}
void merge(int a,int b){
int xa=find(a);
int xb=find(b);
if(xa==xb||xb!=b){
istree=0;//存在环或者某个节点入度超过1
}else fa[xb]=xa;
}
int main(){
int T;
int a,b;
int n,m;
int cnt=0;
while(cin>>a>>b){
if(a==0&&b==0){
printf("Case %d is a tree.\n",++cnt);
}
else if(a<0||b<0)break;
init();
vis[a]++;
vis[b]++;
pre[b]++;
merge(a,b);
while(cin>>a>>b,a||b){
vis[a]++;
vis[b]++;
pre[b]++;
merge(a,b);
}
int sum=0;
for(int i=1;i<=100000;i++){
if(vis[i]&&pre[i]==0)sum++;//判断是否是森林,即入度为0的节点个数
}
if(istree&&sum==1)printf("Case %d is a tree.\n",++cnt);
else printf("Case %d is not a tree.\n",++cnt);
}
system("pause");
return 0;
}
2 判断拼图需要几口井
需要用一个二维数组记录题目信息,然后对每一个节点考虑上下左右是否有merge的可能。最后计算父亲节点有几个。
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++){
for(int k=0;k<4;k++){
int tempy=i+yy[k];
int tempx=j+xx[k];
if(tempy<=0||tempx<=0||tempy>m||tempx>n)continue;
if(k==0&&arr[maps[i][j]-'A'][0]&&arr[maps[tempy][tempx]-'A'][2])merge((i-1)*n+j,(tempy-1)*n+tempx);
if(k==1&&arr[maps[i][j]-'A'][1]&&arr[maps[tempy][tempx]-'A'][3])merge((i-1)*n+j,(tempy-1)*n+tempx);
if(k==2&&arr[maps[i][j]-'A'][2]&&arr[maps[tempy][tempx]-'A'][0])merge((i-1)*n+j,(tempy-1)*n+tempx);
if(k==3&&arr[maps[i][j]-'A'][3]&&arr[maps[tempy][tempx]-'A'][1])merge((i-1)*n+j,(tempy-1)*n+tempx);
}
}
3 求最小生成树中最长边与最短边的差
对于每一次查询,从小到大枚举最短边,对于每一次枚举的最短边,又从最短边到大枚举最长边,当查询的点处于同一个联通分量时结束
while(cin>>n>>m){
cnt=0;//这个题特殊、cnt不能放init里面
init();
for(int i=1;i<=m;i++){
cin>>N[++cnt].a;
cin>>N[cnt].b;
cin>>N[cnt].c;
}
sort(N+1,N+1+cnt,cmp);
cin>>q;
for(int i=1;i<=q;i++){
cin>>a>>b;
ans=999999;
int flag=0;
for(int j=1;j<=cnt;j++){
//因为ans为最大边-最小边,所以从最短的边枚举最小边
//最大边的寻找以a、b同时在一个联通分量中结束
init();
for(int k=j;k<=cnt;k++){
merge(N[k].a,N[k].b);
if(find(a)==find(b)){
flag=1;
ans=min(ans,N[k].c-N[j].c);
break;
//已经找到以j为最小边前提下,让ab同根的最大边
}
}
}
if(flag)cout<<ans<<endl;
else cout<<-1<<endl;
}
}
4 判断所给出的信息能否排序(拓补+并查集)
首先如果是= ,就将它们合并,后期这个连通分量就看成一个点。
else 对于每一次关系,记录到in(用来判断是否0个入度)数组和vector(用来保存孩子)里面。
在solve里边的队列中:
1.如果同时出现了两个及以上的点入度为0,说明信息不全。
2.如果队列结束cnt不为0,说明出现有向环,冲突。
**注意:**在合并=的时候,当他们不处于同一个连通分量才合并,因为合并
void solve(){
queue<int> q;
for(int i=1;i<=n;i++){
if(in[i]==0&&fa[i]==i)q.push(i);//必须要找根节点,大于小于的信息都是以根节点来记录的
}
while(!q.empty()){
int t =q.front();
q.pop();
cnt--;
if(q.size())uncertain=1;//说明同时有多个入度为0的点,这样无法区分顺序
for(int i=0;i<vec[t].size();i++){
in[vec[t][i]]--;
if(!in[vec[t][i]])q.push(vec[t][i]);
}
}
if(cnt)conflict=1;
if(conflict)cout<<"CONFLICT"<<endl;
else if(uncertain)cout<<"UNCERTAIN"<<endl;
else cout<<"OK"<<endl;
}
int main(){
ios::sync_with_stdio(false);
int T;
int a,b;
int m,k,q;
char c;
while(cin>>n>>m){
init();
cnt=n;
for(int i=1;i<=m;i++){
cin>>N[i].a>>N[i].c>>N[i].b;
N[i].a++;
N[i].b++;
if(N[i].c=='='){
if(find(N[i].a)==find(N[i].b))continue;
merge(N[i].a,N[i].b);
cnt--;
}
}//先合并所有同类的点
for(int i=1;i<=m;i++){
a=find(N[i].a);
b=find(N[i].b);
if(N[i].c=='>'){
in[a]++;
vec[b].push_back(a);
}else if(N[i].c=='<'){
in[b]++;
vec[a].push_back(b);
}
}
solve();
}
5 并查集判断小朋友牵手同构
两个图都是一样的操作:最开始先merge,对于每一个连通分量,把该连通分量是否为环,总共几个点,记录在根节点为下标的数组中。然后把所有的连通分量的头节点放入数组中并排序。
逐一比较两个数组,必须完全一样才是同构。
这题能这样的做的原因是: 题目中说每个小朋友只有两只手,所以如果成环一定是圆圈(不可能是局部环)。所以小朋友牵手只有两种情况,一种成链,一种成环。所以通过记录人数和是否成环这两个条件就足以判断是否同构。
void merge(int a,int b){
int xa=find(a);
int xb=find(b);
if(xa!=xb){
fa[xb]=xa;
person[xa]+=person[xb];
}
else iscircle[xa]=1;
}
for(int v=1;v<=T;v++){
init();
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b;
merge(a,b);
}
int cnt1=0;
for(int i=1;i<=n;i++){
if(fa[i]==i){
G1[++cnt1].person=person[i];
G1[cnt1].iscirle=iscircle[i];
}
}
sort(G1+1,G1+1+cnt1,cmp);
init();
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a>>b;
merge(a,b);
}
int cnt2=0;
for(int i=1;i<=n;i++){
if(fa[i]==i){
G2[++cnt2].person=person[i];
G2[cnt2].iscirle=iscircle[i];
}
}
sort(G2+1,G2+1+cnt2,cmp);
int flag=1;
if(cnt1!=cnt2)flag=0;
if(flag){
for(int i=1;i<=cnt1;i++){
if(G1[i].person!=G2[i].person||G1[i].iscirle!=G2[i].iscirle)flag=0;
}
}
cout<<"Case #"<<v<<": ";
if(flag)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
6
题意很恶心,如果有两个点a,b,a到b有很多路径。而T就是min{路径1的最长边,路径2的最长边。。。。}。
1.对所有边排序,每次把边拿出来merge直至小于给出的T。对于不同根的两个点,这条边一定是题给中的最长边,因为这是必经边。
2.对查询进行排序,从小到大可以只操作一次,这样可以省时间。
3.本人思索很久的是:出现环为什么直接return 0.根据题目的意思和别人的博客,成环的那条边显然大于所有边,但是为何不记录?答:题意中的how many kind of path 指的是有多少组点对。题意不清晰
#include<bits/stdc++.h>
using namespace std;
const int INF=10001;
int fa[INF+1];
int person[INF+1];
int iscircle[INF+1];
int answer[INF+1];
int n;
struct node{
int a,b,c;
}N[INF*5];
struct ans{
int val;
int pos;
}A[INF];
int cmp(node a,node b){
return a.c<b.c;
}
int cmp1(ans a,ans b){
return a.val<b.val;
}
int find(int x){
if(x==fa[x])return x;
else return fa[x]=find(fa[x]);
}
void init(){
for(int i=1;i<=INF;i++){
fa[i]=i;
person[i]=1;//表示连通分量人数
}
}
int merge(int a,int b){
int xa=find(a);
int xb=find(b);
if(xa==xb)return 0;//
if(xa!=xb){
fa[xb]=xa;
int sum=person[xa]*person[xb];
person[xa]+=person[xb];
return sum;
}
}
int main(){
ios::sync_with_stdio(false);
int T;
int a,b;
int m,k,q;
char c;
while(cin>>n>>m>>k){
init();
for(int i=1;i<=m;i++)
cin>>N[i].a>>N[i].b>>N[i].c;
sort(N+1,N+1+m,cmp);
for(int i=1;i<=k;i++){
cin>>A[i].val;
A[i].pos=i;
}
sort(A+1,A+1+k,cmp1);
int j=1;
int sum=0;
for(int i=1;i<=k;i++){
//根据题意,不可能出现环,所以每条边都是新路径的必经路径
//而又由于排序,这条路径一定是所谓的最长边,只要让最长边小于t就行
while(j<=m&&N[j].c<=A[i].val){
sum+=merge(N[j].a,N[j].b);
j++;
}
answer[A[i].pos]=sum;
}
for(int i=1;i<=k;i++)
cout<<answer[i]<<endl;
}
system("pause");
return 0;
}