图论相关 每日训练

先是热身题 图论相关求割点 求拓扑排序在后面
蓝桥题库 算法提高 套正方形
官网oj没过,但是输出没有一点问题,不知道哪里错了

//蓝桥杯 算法提高 套正方形 
#include<iostream>
#include<cstring>
using namespace std;

const int MAXN=52;
int main(){
	char a[MAXN][MAXN];
	int n;
	int left,right;
	cin>>n;
	
	for(int i=1;i<=n;i++){
		for(int j=0;j<n;j++){
			if(j==0||j==n-1)
				a[i][j]='*';
			else
				a[i][j]=' ';	
		}
	}
		
	
	for(int i=1;i<=n/2;i++){		//第一行第一列为a[1][0] 
		if(i%2==1){			//行数为奇数的时候才更新
			left=i-1;
			right=n-i;
		}
		a[i][left]=a[i][right]='*';		//用left、right控制字符的位置
		if(i%2==1){
			for(int j=left+1;j<=right-1;j++)
				a[i][j]='*';
		}

		strcpy(a[n+1-i],a[i]);			//图形是上下对称的
	}
	
	for(int i=1;i<=n;i++){
		for(int j=0;j<n;j++)
			cout<<a[i][j];
		if(i==n)				//防止最后一行多输出空行 
			break;	
		cout<<endl;
	}
	
	return 0;
}

再来一道 Monday-Saturday质因子
问题描述
  这个问题是个简单的与数论有关的题目,看起来似乎是“求正整数的所有质因子”,但实际上并不完全是这样。

本题中需要定义以下几个概念:
  1. Monday-Saturday数
  对于一个正整数N,如果它除以7得到的余数是1或6,则可以写成N=7k+{1,6}的形式。更形象的,我们把这样的N称作“Monday-Saturday数”,简称“MS数”。
  2. Monday-Saturday因子
  如果对于两个MS数a,b,若存在一个MS数x,使得ax=b,那么就称a是b的一个“Monday-Saturday因子”,简称“MS因子”。
  3. Monday-Saturday质数
  如果对于MS数a,满足a>1且除了1和a之外a没有其他的MS因子,那么称a是一个“Monday-Saturday质数”,简称“MS质数”。
  注:对于传统意义上的质数,若它是一个MS数,则它一定是一个MS质数。但反之不必成立,例如27,它是一个MS质数但不是传统意义上的质数。
  4. Monday-Saturday质因子
  如果对于两个MS数a,b,若满足a是b的MS因子且a是一个MS质数,那么称a是b的一个“Monday-Saturday质因子”。
  例如:27是216的一个MS质因子(216=278)。
  问题就是,给定一个MS数N,求其所有的Monday-Saturday质因子。
  
注意,如果a是b的质因子,那么存在x,使得a
x=b,x也必须是MS数,见第二条规则,所以求质因子,就需要判断一个因子(a)是MS数,而且是MS质数,另一个因子(x)是MS数。
  代码如下

//蓝桥杯 算法提高 ADV-287 Monday-Saturday质因子
#include<iostream>
#include<vector>
#include<set>
#include<cmath>
#include<algorithm>
using namespace std;

const int MAXN = 300000 + 2;
set<int> msnum;			//存放MS数
set<int> prime;			//存放MS质数
set<int> ans;			//输出的质因子的集合

void pprint(int a) {
	cout << " " << a;
}

int main() {
	int num;

	for (int i = 7;  i < MAXN; i = i + 7) {		//获取MS数
		msnum.insert(i - 1);
		msnum.insert(i + 1);
		prime.insert(i - 1);
		prime.insert(i + 1);
	}
	set<int>::iterator IT;
	set<int>::iterator dd;
	for (IT = msnum.begin(); IT != msnum.end(); IT++) {
		if (*IT > sqrt(MAXN))				//与埃式筛法相同,若a存在因子,必定有一个小于a^(1/2)
			break;
		for (dd = IT; dd != msnum.end(); dd++) {
			if ((long long int)(*IT)*(*dd) > MAXN)	//long long防止乘法超出int最大值
					break;
			prime.erase((*IT)*(*dd));			//剔除一些数,获得MS质数集合
		}
	}
	msnum.insert(1);			//MS数有1  但是MS质数无1

	while (cin >> num) {
		ans.clear();
		if (num == 1)
			break;
		for (IT = prime.begin(); IT !=prime.end(); IT++) {		//满足质数要求
			if ((num % (*IT)) == 0 && msnum.count(num/(*IT)))			//满足因数要求
				ans.insert(*IT);
			if (*IT > num)			//大于的情况就不用考虑了
				break;
		}
		cout << num << ":";
		for_each(ans.begin(), ans.end(), pprint);
		cout << endl;
	}
	return 0;
}

接下来是关于图的,拓扑排序,很多问题都会涉及到,bfs和dfs都可以实现(代码如下),dfs更方便一些,因为dfs递归回退的顺序就是拓扑排序的逆序。
bfs步骤:
(1)找到所有入度为0的点,放进队列,作为起点,谁先谁后没关系。如果找不到入度为0的点,说明这个图不是DAG,不存在拓扑排序。
(2)弹出队首a,a的所有邻居点的入度减1,把入度为0的邻居点放进队列。
(3)继续上述操作,直到队列为空。
如果队列已空,但是还有顶点没有进入队列,那么说明这些点入度都不为0,该图不是DAG。
dfs步骤:
一个有向无环DAG,如果只有一个点u是0入度的,那么从u开始dfs,dfs递归返回的顺序就是拓扑排序(逆序)。dfs递归返回的首先是最底层的点,它一定是0出度点,没有后续点,是拓扑排序的最后一个点;然后逐步回退,最后输出的是起点u,输出的顺序是一个逆序。

//拓扑排序
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<stack>
using namespace std;

const int MAXN=100+1;
vector<int> graph[MAXN];		//graph[a][]记录以a为始边,能直达的顶点 
int degree[MAXN];			//顶点的入度信息 
int N;		//边数 
int V;  	//顶点数 
int vis[MAXN];			//方便递归调用
stack<int> S;
queue<int> Q;
	
void Topological_bfs(){
	for(int i=1;i<=V;i++)		//先找出入度为0的顶点,开始bfs
		if(degree[i]==0){
			Q.push(i);
			degree[i]=-1;		//更新degree信息,防止再次入队
			cout<<i<<" ";		//输出顶点信息
		}
	if(Q.size()==0){		//该图有环,无法拓扑排序 
		cout<<"The Circle has circle.Not DAG"<<endl;
		return;
	}
	int u;
	while(!Q.empty()){
		u=Q.front();
		Q.pop();
		for(int i=0;i<graph[u].size();i++){		//去除顶点u后,更新入度表 
			degree[graph[u][i]]--;
		}
		
		for(int i=1;i<=V;i++)		//遍历顶点,判断哪些可以入队 
			if(degree[i]==0){
				Q.push(i);
				degree[i]=-1;		//同样要更新每个顶点的degree
				cout<<i<<" ";
			}
	}
}

void Topological_dfs(int u){
	for(int i=0;i<graph[u].size();i++){
		if(!vis[graph[u][i]]){
			vis[graph[u][i]]=1;
			Topological_dfs(graph[u][i]);
			S.push(graph[u][i]);
		}
	}
	
	
}

int main(){
	memset(vis,0,sizeof(vis));
	memset(degree,0,sizeof(degree));
	
	int a,b;
	cin>>N>>V;
	for(int i=0;i<N;i++){
		cin>>a>>b;				//有向边a->b 
		graph[a].push_back(b);
		degree[b]++;
	}
	
	for(int i=1;i<=V;i++)
		if(degree[i]==0){		//找起点
			vis[i]=1;
			Topological_dfs(i);
			S.push(i);			//入栈,因为递归返回的顺序是拓扑排序的逆序
		}
	while(!S.empty()){
		cout<<S.top()<<" ";
		S.pop();	
	}
	
	cout<<endl<<"-----------"<<endl;
	Topological_bfs();
	
	return 0;
}

接着,继续图论,割点的判断,先理解两个定理。
定理1:T的根节点s是割点,当且仅当s有两个或更多的子节点。
定理2:T的非根节点u是割点,当且仅当u存在一个子节点v,v及后代都没有回退边连回u的祖先。
定义num[u]记录dfs对每个结点的访问顺序,num随着递归深度增大而增大。
定义low[v]记录v和v的后代能连回到的祖先num。
只要low[v]>=num[u],就说明v在这个支路上没有回退边连回u的祖先最多退到u本身,即u是割点。

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 109;
int low[N], num[N], dfn;			//dfn记录递归的顺序,用于给num赋值 
bool iscut[N];
vector<int> graph[N];			//存图 

void dfs(int u, int fa) {			//u的父节点是fa 
	low[u] = num[u] = ++dfn;		//初始值 
	int child=0;					//孩子数目 
	for (int i = 0; i < graph[u].size(); i++) {		//处理u的所有子节点 
		int v = graph[u][i];
		if (!num[v]) {			//v没访问过 
			child++;
			dfs(v, u);
			low[u] = min(low[v], low[u]);		//用后代返回值更新ow值 
			if (low[v] >= num[u] && u != 1)
				iscut[u] = true;			//标记割点 
		}
		else if (num[v] < num[u] && v != fa)		//处理那些与已经访问过的点连通的点,v!=fa,fa是u的父节点 
			low[u] = min(low[u], num[v]);
	}
	if (u == 1 && child >= 2)		//处理根节点的特殊情况
		iscut[1] = true;
}

int main() {
	memset(low, 0, sizeof(low));
	memset(num, 0, sizeof(num));
	memset(iscut, false, sizeof(iscut));

	int ans, n;
	int a, b;

	ans = 0;
	dfn = 0;

	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a >> b;
		graph[a].push_back(b);
		graph[b].push_back(a);
	}

	dfs(1, -1);
	for (int i = 1; i <= n; i++)
		ans += iscut[i];
	cout << ans << endl;
	return 0;
}
发布了17 篇原创文章 · 获赞 2 · 访问量 438

猜你喜欢

转载自blog.csdn.net/Raymond_YP/article/details/104270643