这周的题目难的非常之难,难到祝宝专门录了个视频(以第一题为例)给我们讲解。
本质上是图的新知识与上周搜索的结合。个人认为这三道题反而应该归到搜索的专题大类里面,因为新知识虽然也是数据结构里面的内容,但主要还是看解题思路,工具还是要用搜索的。
危险系数
这道题是祝宝全程讲解过的,虽然如此我还是wr了一次,先看题目。
思路就是从起点开始直接深搜(因为是无向图),然后把访问过的都标记一遍,最终输出ans。
一开始wr是因为访问的数组下标写成了0......后面才发现。
需要注意的是必须给初始的访问加true的判定,因为dfs里是优先判定tf再修改tf的。而且还要给ans减2(头和尾)。这里还发现祝宝的一个小错误,“两点不连通则输出-1”。这个祝宝漏了,但是因为题目数据没给不连通的,所以祝宝ac了没发现。
代码:
#include<bits/stdc++.h>
using namespace std;
int N=1005;
int n,m;
bool visit[1005];
vector<int>G[1005];
int start;
int ed;
int total;
int cnt[1005];
void dfs(int now)
{
if(now==ed)
{
total++;
for(int i=1;i<=n;i++)//访问必须符合编号,要从零开始......
{
if(visit[i])
cnt[i]++;
}
return;
}
for(int i=0;i<G[now].size();i++)
{
int to=G[now][i];
if(!visit[to])
{
visit[to]=true;
dfs(to);
visit[to]=false;
}
}
return;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x;cin>>x;
int y;cin>>y;
G[x].push_back(y);
G[y].push_back(x);
}
cin>>start;
cin>>ed;
visit[start]=true;//初始赋值
dfs(start);
int ans=0;
for(int i=1;i<=n;i++)
{
if(cnt[i]==total)
{
ans++;
}
}
ans-=2;//去头去尾
if(ans==0) cout<<-1;
else cout<<ans;
return 0;
}
图的遍历
一般第二题都是第一题的进阶版或者直接考察另外的知识(比如上周第一题是dfs,第二题是bfs),这周的第二题便是第一题的进阶。
首先题目就很有意思。明明是从v出发求最大能到达的点,但是建边的时候,方向却是U->V。同时,与一般题目问最多走几步不同,此题问的是能走到的最大编号 。
因而,通过“有向”的这个条件,想到可以反向建边。直接从最大编号开始反向dfs,这样只要访问过,便是最终答案(最大编号)。
代码:
#include<bits/stdc++.h>
using namespace std;
int N,M,A[100005];
vector<int>G[100005];
void dfs(int x, int y) {
if(A[x]!=0) //只要访问过,就返回(因为是从大到小进行dfs的)
return;
A[x] = y;//y就是终点,同时也表明了较小点(x点)能到达的最大点
for(int i=0; i<G[x].size(); i++)
dfs(G[x][i], y);//回溯y
}
int main() {
int U, V;
cin>>N>>M;
for(int i=1; i<=M; i++)
{
cin>>U>>V;
G[V].push_back(U); //反向建边
}
for(int i=N; i>0; i--)//直接从最大编号开始,这样dfs只要访问一次
dfs(i, i); //第一个i是变量(反向往前),第二个i是终点(能到达的最大点)
for(int i=1; i<=N; i++)
{
cout<<A[i];
if(i!=N)
cout<<" ";
}
cout<<endl;
return 0;
}
封锁阳光大学
这道题是真的难(至少对我来说),难到题解的第二高赞竟然是对第一高赞题解的补充说明,这也是我第一次看到纯文字无代码的高赞题解。能自己做出来这题的人是真的强。
这道题的题干可以总结为,每条边都只有一个端点被河蟹占领。又,这个图肯定是一张联通图(虽然一开始就知道不可能用一整张图解决,但是联通图涉及到我的知识盲区了(真不知道这个))所以一个联通图可以全部选染成一种色的,也可以全部选染成另一种色的(或者impossible)。然后记录访问过的点,如果重复那肯定冲突。
至于写法方面,存边使用结构体而不是容器着实打破了我的僵化思维。
代码:
#include <bits/stdc++.h>
using namespace std;
struct edge
{
int t;
int nexty;
}edge[200000];
int head[20000];
int cnt=0;
void add(int a,int b)//存边
{
cnt++;
edge[cnt].t=b;
edge[cnt].nexty=head[a];
head[a]=cnt;
}
bool visit[20000]={0};//是否遍历过
int col[20000]={0};//每一个点的染色
int sum[2];//黑白两种染色各自的点数
bool dfs(int x,int color)//染色
{
if(visit[x])//如果已被染过色
{
if(col[x]==color)return true;//如果仍是原来的颜色,即可行
return false;//非原来的颜色,即产生了冲突,不可行
}
visit[x]=true;//记录
sum[col[x]=color]++;//这一种颜色的个数加1,且此点的颜色也记录下来
bool tof=true;//是否可行
for(int i=head[x];i!=0&&tof;i=edge[i].nexty)//遍历边
{
tof=tof&&dfs(edge[i].t,1-color);//是否可以继续染色
}
return tof;//返回是否完成染色
}
int main()
{
int n,m;
cin>>n>>m;
int a,b;
while(m--)
{
cin>>a>>b;
add(a,b);
add(b,a);//存的是有向边,所以存两次
}
int ans=0;
for(int i=1;i<=n;i++)
{
if(visit[i])continue;//如果此点已被包含为一个已经被遍历过的子图,则不需重复遍历
sum[0]=sum[1]=0;//初始化
if(!dfs(i,0))//如果不能染色
{
cout<<"Impossible";
return 0;//直接跳出
}
ans+=min(sum[0],sum[1]);//加上小的一个
}
cout<<ans;//输出答案
return 0;
}