上周图论,这周也是,因为好难(哭死)
P3367 【模板】并查集
区区模版题,写了我好久。。
题设说需要完成一个并查集的合并和查询操作
遇到1就合并,2就判断
很简单,可是合并会有两种情况,一种是一棵树只有一个分叉,以及一棵树有很多分叉。显而易见,第二种时间复杂度更低,所以我们需要对其进行路径优化
if(pre[x]==x)
return x;
return pre[x]=fa(pre[x]);
pre[]是x的前驱,代表x的父节点
不断寻找父节点,当查找到父节点是其本身时,这个节点就是祖宗节点。合并就是寻找两个节点的祖宗节点,如果两个节点不相同,那么就把祖宗节点合并
OK啦。
下面是代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e4+5;
int pre[N];
int x,y,z;
int fa(int x)
{
if(pre[x]==x)
return x;
return pre[x]=fa(pre[x]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
pre[i]=i;
for(int i=1;i<=m;i++)
{
cin>>z>>x>>y;
if(z==1)
pre[fa(x)]=fa(y);
if(z==2)
{
if(fa(x)==fa(y))
cout<<"Y"<<endl;
else
cout<<"N"<<endl;
}
}
return 0;
}
P8604 [蓝桥杯 2013 国 C] 危险系数
这题其实很水啦
已知两个点start和target为起点和终点,然后i遍历每个点,并借助一个辅助数组存入除i以外的所有点之间的距离,判断i点失去后,能否从起点到达终点,如果能那就一直return return,如果不能,那就最后出去,并做标记,并将这一个点计入关键点
#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff
const int N=1e3+5;
int mapp[N][N];
int ff[N][N];
int u,v,n,m;
int vis[N];
int f,flag;
int ans;
int start;
int target;
bool dfs(int sta)
{
if(sta==target||f)
{
f=1;
return true;
}
for(int i=1;i<=n;i++)//i=2
{
if(f)
return true;
if(!vis[i]&&ff[i][sta])
{
vis[i]=1;
dfs(i);
if(f)
return true;
vis[i]=0;
}
}
return false;
}
int main()
{
cin>>n>>m;
memset(mapp,0,sizeof(mapp));
for(int i=1;i<=m;i++)
{
cin>>u>>v;
mapp[u][v]=mapp[v][u]=1;
}
cin>>start>>target;
for(int i=1;i<=n;i++)
{
if(i==start||i==target)
continue;
memset(vis,0,sizeof(vis));
memset(ff,0,sizeof(ff));
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
if(j!=i&&k!=i)
{
ff[j][k]=mapp[j][k];
ff[k][j]=mapp[k][j];
}
vis[start]=1;
if(!dfs(start))//起点为start,判断能否到终点target
flag=1,ans++;
f=0;
}
if(flag)
cout<<ans;
else
cout<<-1;
return 0;
}
P1330 封锁阳光大学
这题,挺抽象的。。又又又用了没学过的东西,二分图
二分图:
在一张图中,如果能够把全部的点分到 两个集合 中,保证两个集合内部没有任何边 ,图中的边只存在于两个集合之间,这张图就是二分图
bfs思路:
不和谐的河蟹,可以看做对空白点位进行染色,无色染成红色或蓝色(0->1,2),先来个双向存图,然后还可以发现:对于所有情况,有以下四种可能
1、两相邻节点染色且同色,不过河蟹会干架,不可行
2、两相邻节点染色但异色,可行
3、两相邻节点均没有染色,没有完成封锁,不可行
4、两相邻节点有一个没有染色,那么染成异色,象征不放河蟹
文章至此,后面的思路就很好想了,直接上bfs模版
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int u,v;
const int N=2e5+10,M=2e5+10;
int paint[N];//
int ans;
int cou[5];
struct Edge
{
int to;
int next;
}edge[M];
int cnt;
int head[N];
void add(int u,int v)//前向星存图
{
cnt++;
edge[cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
queue<int> q;
bool bfs(int sta)
{
paint[sta]=1;//起点染色1
cou[1]=1;
cou[2]=0;//初始化
q.push(sta);
while(q.size())
{
int k=q.front();
q.pop();
for(int j=head[k];j;j=edge[j].next)
{//遍历以head[k]开始的通路
int v=edge[j].to;
if(paint[k]==paint[v])
return 1;//下一条边与这条边颜色相同
if(!paint[v])//没有染色
{
paint[v]=3-paint[k];//染不同颜色
cou[paint[v]]++;
q.push(v);
}
}
}
return 0;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);//双向建图,方便遍历
}
for(int i=1;i<=n;i++)
{
if(paint[i])//如果第i条边已染色,则不遍历
continue;
if(bfs(i))
{
cout<<"Impossible"<<endl;
return 0;
}
else
ans+=min(cou[1],cou[2]);
}
cout<<ans;
return 0;
}
dfs思路:
由题目意思:可以将所有情况抽象成两个要素
1、每一条边所连接的点中,至少要有一个被选中
2、每一条边所连接的两个点,不能被同时选中
那么可以得出:每一条边都有且仅有一个被它所连接的点被选中
所以,相邻两个点染成不同颜色
因此首先将所有点先染成同一种颜色(黑白),然后取两种颜色中最小值,最后累加即可
(由于回将所有点遍历,因此最后累加结果是最后图上异色点数量较小点的值)
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=2e5+5;
struct Edge
{
int v;
int next;
}edge[N];
int u,v;
int cnt;
int cou[3];
int ans;
int head[N];
int paint[N];
int vis[N];
void add(int u,int v)
{
cnt++;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
bool dfs(int sta,int color)
{
if(vis[sta])
{
if(paint[sta]==color)
return true;
return false;
}
vis[sta]=true;
cou[paint[sta]=color]++;
bool tt=true;
for(int i=head[sta];i&&tt;i=edge[i].next)
tt=tt&&dfs(edge[i].v,1-color);
return tt;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
add(u,v);
add(v,u);
}
for(int i=1;i<=n;i++)
{
if(vis[i])
continue;
cou[0]=cou[1]=0;
if(!dfs(i,0))
{
cout<<"Impossible";
return 0;
}
else
ans+=min(cou[0],cou[1]);
}
cout<<ans;
return 0;
}
P3916 图的遍历
本题思路挺新奇,需要反向存图,因为这样就可以保证由大的数到小的数的顺序来遍历。将所有经过的数都赋予一开始的那个最大的值,与其他值不联通的就是它自己
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int cnt;
int vis[N];
int u,v;
struct Edge
{
int v;
int next;
}edge[N];
int head[N];
int f[N];
void add(int u,int v)
{
cnt++;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u,int fa)
{
f[u]=fa;
for(int i=head[u];i;i=edge[i].next)
{
int vv=edge[i].v;
if(f[vv])
continue;
dfs(vv,fa);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v;
add(v,u);
}
for(int i=n;i>=1;i--)
{
if(f[i])
continue;
dfs(i,i);
}
for(int i=1;i<=n;i++)
cout<<f[i]<<" ";
}
P1119 灾后重建
这题看起来虽然很麻烦,不过不过,思路还是很好想的,这就是很单纯一个Floyd
可是按照模版直接写会爆,因此需要稍稍剪枝
因为t是单增的,因此用vis[]判断是否有遍历过,其他就与Floyd一模一样了
代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int n;//村庄数目
int m;//公路数量
const int N=205;
int mapp[N][N];
int t[N];//村庄修复时间
int x,y,w;
int tt;
int Q;
int dis[N][N];
int vis[N];
int main()
{
cin>>n>>m;
memset(dis,inf,sizeof(dis));
for(int i=1;i<=n;i++)
cin>>t[i];//村庄修复时间
for(int i=1;i<=m;i++)
{
cin>>x>>y>>w;
dis[x+1][y+1]=dis[y+1][x+1]=w;//mapp[i][j]--村庄i与村庄j距离
}
cin>>Q;
for(int i=1;i<=Q;i++)
{
cin>>x>>y>>tt;
x++,y++;
for(int k=1;k<=n;k++)
{
if(t[k]<=tt&&!vis[k])//剪枝
{
vis[k]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
if(t[x]<=tt&&t[y]<=tt&&dis[x][y]!=inf)
cout<<dis[x][y]<<endl;
else
cout<<-1<<endl;
}
return 0;
}