本周学习内容(以下题目所涉及的):
1.并查集 2.临接表存储图 3.dfs求每个点走过次数 4.链式前向星存储图 5. 黑白染色 6.反向建图 7.Floyd算法;
一.P3367 【模板】并查集
思路:本题为并查集的模版,是本周唯一友好的题目。
本题使用并查集+压缩节点;f[ ]数组存储各点的祖先,fa( x )函数负责找到一个点(x)的祖先节点。至于压缩路径,fa()与f[]其实他们的压缩路径就是记忆化的斐波那契数列的思路。
code
#include<iostream>
using namespace std;
#define M 1e5
int f[100002],n,m;
int fa(int x)
{
if (f[x]!=x)
f[x]=fa(f[x]); //更新祖先节点,压缩路径。
return f[x];
}
int main()
{
cin>>n>>m;
for (int i=0;i<=n;i++)
f[i]=i;
while (m--)
{
int z,x,y;
cin>>z>>x>>y;
if (z==1)
{
//f[x]=y;
f[fa(x)]=fa(y);
}
else
{
if (fa(x)==fa(y)) cout<<"Y"<<endl;
else cout<<"N"<<endl;
}
}
return 0;
}
二.P8604 [蓝桥杯 2013 国 C] 危险系数
思路:可以使用 dfs(深度优先搜索)求解,求出 u 到 v 间的每一条路径,将路径总数统计,并将被经过的点被经过总数加一。如果一个点被经过的次数与总路径条数相等,那么这一个点就是 u 和 v 的关键点。
code
#include<iostream>
#include<vector>
#define int long long
using namespace std;
vector<int>edges[1010];//临接表存储图
vector<bool>visited(1010);//记录是否走过
vector<int>vexnum(1010);//记录每个点走过次数
int n, m, start, endd, ans = 0, sum = 0;
void dfs(int cur) {
if (cur == endd) {
sum++;//路径总数
for (int i = 1; i <= n; i++) {
if (visited[i]==1) {
vexnum[i]++;//每个点走过次数
}
}
}
else {
int len = edges[cur].size();
for (int i = 0; i < len; i++) {
int value = edges[cur][i];
if (!visited[value]) {
visited[value] = 1;
dfs(value);
visited[value] = 0;
}
}
}
}
signed main()
{
cin >> n >> m;
int v1, v2, len;
for (int i = 0; i < m; i++) {
cin >> v1 >> v2;
edges[v1].push_back(v2);
edges[v2].push_back(v1);
}
cin >> start >> endd;
visited[start] = 1;
dfs(start);
if (sum > 0) {
for (int i = 1; i <= n; i++) {
if (vexnum[i] == sum) {//是否存在点走过次数与路径总数相等的点
ans++;//关键点计数
}
}
cout << ans - 2 << endl;//减2是因为统计时多加了起点和终点
}
else {
cout << "-1" << endl;
}
return 0;
}
三.P1330 封锁阳光大学
思路:链式前向星+黑白染色
首先,肯定要明确一点,那就是这个图是不一定联通的。于是,我们就可以将整张图切分成许多分开的连同子图来处理。
题意说白了就是:每一条边都有且仅有一个被它所连接的点被选中。又因为我们要处理的是一个连通图。所以,对于这一个图的点的选法,可以考虑到相邻的点染成不同的颜色。于是,对于一个连通图,要不就只有两种选法(因为可以全部选染成一种色的,也可以全部选染成另一种色的),要不就是impossible!
所以,我们只需要找到每一个子连通图,对它进行黑白染色,然后取两种染色中的最小值,然后最后汇总,就可以了。
code
#include<bits/stdc++.h>
using namespace std;
const int M=200001;
struct edge
{
int to,nexth;
} e[M];
int head[M],cnt=0;
void add(int f,int t)
{
cnt++;
e[cnt].to=t;
e[cnt].nexth=head[f];
head[f]=cnt;
}
bool vis[M];
int col[M],sum[2];//col每一个点的染色; sum黑白两种染色各自的点数
bool dfs(int node,int color)//染色(返回false即impossible)
{
if(vis[node])//如果已被染过色
{
if(col[node]==color) return true;//如果仍是原来的颜色,即可行
return false;//非原来的颜色,即产生了冲突,不可行
}
vis[node]=true;//记录
sum[col[node]=color]++;//这一种颜色的个数加1,且此点的颜色也记录下来
bool tf=true;//是否可行
for(int i=head[node];i!=0&&tf;i=e[i].nexth)//遍历边
{
tf=dfs(e[i].to,1-color);//是否可以继续染色
/*这道题不用回溯。只有在那些需要求出所有可能路径的题目中才需要回溯操作。
为什么呢?其实回溯操作类似于一个“遗忘操作”;使得在寻找第二条路径时可以找到这个相同的结点。
而这道题并不需要求出所以可能路径。只要把所有的结点都走一遍,并不是要求出所有可能路径。*/
}
return tf;//返回是否完成染色
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int a,b;
while(m--)
{
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);//存的是有向边,所以存两次
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;//如果此点已被包含为一个已经被遍历过的子图,则不需重复遍历
sum[0]=sum[1]=0;//初始化
if(!dfs(i,0))//如果不能染色
{
printf("Impossible");
return 0;//直接跳出
}
ans+=min(sum[0],sum[1]);//加上小的一个 可能不在一个连通图
}
printf("%d",ans);//输出答案
return 0;
}
四.P3916 图的遍历
思路:反向建边 + dfs
按题目来每次考虑每个点可以到达点编号最大的点,不如考虑较大的点可以反向到达哪些点
循环从N到1,则每个点i能访问到的结点的A值都是i
每个点访问一次,这个A值就是最优的,因为之后如果再访问到这个结点那么答案肯定没当前大了
code
#include <bits/stdc++.h>
using namespace std;
const int M = 1e5+2;
int n,m,res[M];
vector <int> q[M];
void dfs(int now1,int goal)
{
res[now1]=goal;
for(int k=0;k<q[now1].size();k++)
{
int from=q[now1][k];
if (res[from]==0)
{
dfs(from,goal);
}
}
}
int main()
{
cin>>n>>m;
while (m--)
{
int x,y;
cin>>x>>y;
q[y].push_back(x);
}
for(int i=n;i>=1;i--){
if(res[i]==0){
//res[i]=i;
dfs(i,i);
}
}
for(int i=1;i<=n;i++) printf("%d ",res[i]);
return 0;
}
五.P1119 灾后重建
思路:Floyd算法
/*Floyd算法:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。*/
/*本题思路:所有的边全部给出,按照时间顺序更新每一个可用的点(即修建好村庄),对于每个时间点进行两点之间询问,求对于目前建设的所有村庄来说任意两点之间的最短路不正好就是Floyd算法中使用前k个节点更新最短路的思维吗?*/
code
#include<bits/stdc++.h>
using namespace std;
const int N=202;
int f[N][N],n,m,a[N],vis;
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);//依次输入每一个村庄建立完成时需要的时间
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
f[i][j]=0x3f3f3f3f;//初始化为保证它不爆炸范围内的最大值
for(int i=0;i<n;i++)
f[i][i]=0;
int s1,s2,s3;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&s1,&s2,&s3);
f[s1][s2]=s3;
f[s2][s1]=s3;
}
int x,y,tt,num;
cin>>num;
while(num--)
{
cin>>x>>y>>tt;
for (int i=0;i<n;i++)
{
if (vis!=0)
if (a[i]<=vis) continue;
if (a[i]>tt) continue;
for (int j=0;j<n;j++)
for (int k=0;k<n;k++)
{
f[j][k]=min(f[j][k],f[j][i]+f[k][i]);
f[k][j]=f[j][k];
//cout<<f[x][y]<<endl;
}
}
if(a[x]>tt||a[y]>tt)cout<<-1<<endl;
else
{
if(f[x][y]==0x3f3f3f3f)cout<<-1<<endl;
else cout<<f[x][y]<<endl;
}
vis=max(vis,tt);
}
return 0;
}