week 5 图

这周的题目难的非常之难,难到祝宝专门录了个视频(以第一题为例)给我们讲解。

本质上是图的新知识与上周搜索的结合。个人认为这三道题反而应该归到搜索的专题大类里面,因为新知识虽然也是数据结构里面的内容,但主要还是看解题思路,工具还是要用搜索的。

危险系数

这道题是祝宝全程讲解过的,虽然如此我还是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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无用夜宵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值