先是热身题 图论相关求割点 求拓扑排序在后面
蓝桥题库 算法提高 套正方形
官网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,使得ax=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;
}