主要集中一些初学图论的基础问题
给出N个点,M条边的有向图,对于每个点v,求A(v)表示从点vv出发,能到达的编号最大的点。
4 3
1 2
2 4
4 3
4 4 3 4
建图很简单,但是要到达最大的点,dfs每个点那么会T,最好的办法是反向存图,看最大的边可以到达哪些点并进行标记就行了
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
#define MAXL 100010
int N, M, A[MAXL];
vector<int> G[MAXL]; //vector存图
void dfs(int x, int d) //d保留最大值,x表示现在的结点
{
if(A[x]) return; //访问过
A[x] = d;
for(int i=0; i<G[x].size(); i++)
dfs(G[x][i], d);
}
int main()
{
int u, v;
scanf("%d%d", &N, &M);
for(int i=1; i<=M; i++) {
scanf("%d%d", &u, &v);
G[v].push_back(u); //反向建边
}
for(int i=N; i; i--) dfs(i, i);
for(int i=1; i<=N; i++) printf("%d ", A[i]);
printf("\n");
return 0;
}
关于欧拉路的题目放两道
证明欧拉路和欧拉回路:
奇点:度数为奇数的点
欧拉路:只有两个奇点连通的图在这里插入代码片
欧拉回路:没有奇点连通的图
求欧拉路:先判断奇点,再dfs
欧拉回路:对于任意一个点执行dfs
欧拉路:对奇点执行dfs
给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒)。请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现。
第一行输入一个正整数n。
以下n行每行两个字母,表示这两个字母需要相邻。
输出满足要求的字符串。
如果没有满足要求的字符串,请输出“No Solution”。
如果有多种方案,请输出前面的字母的ASCII编码尽可能小的(字典序最小)的方案
4
aZ
tZ
Xt
aX
XaZtX
本题其实问的是:给出一个无向图,求字典序最小的一条欧拉路径(自己可以画一个图就容易懂了)
注意点:
1、是否是连通图,用并查集判断,只有一个根
2、是否是欧拉路或者欧拉回路,判断奇点个数
3、dfs求欧拉路径
#include<bits/stdc++.h>
using namespace std;
//ascii不大于128,所以取130
const int N=52*51/2+10; //每个字母可以和51个字母想连,所以字符个数为52*51/2
int f[130]; //并查集父结点,检查根的个数
int m[130][130],d[130]; //数据量不大,m为邻接矩阵,d记录度数
char ans[N]; //记录输出的字符串
int n;
void init() { //初始化
for(int i=64; i<130; i++ ) f[i]=i;
memset(m,0,sizeof(m));
memset(d,0,sizeof(d));
}
int find(int x) {
return x==f[x]?x:f[x]=find(f[x]);
}
void unions(int x, int y) {
int fx=find(x);
int fy=find(y);
if ( fx!=fy ) f[fx]=fy;
}
void dfs(int head) { //路径搜索
for(int i=64; i<130; i++ ) {
if ( m[head][i] ) {
m[head][i]=m[i][head]=0;
dfs(i);
}
}
ans[n--]=head; //在回溯部分记录,因此需要倒序输出
}
int main() {
char s[2];
scanf("%d",&n);
init(); //初始化
for(int i=0; i<n; i++ ) {
scanf("%s",s);
m[s[0]][s[1]]=m[s[1]][s[0]]=1; //记录邻接矩阵
unions(s[0],s[1]); //记录父结点
d[s[0]]++; //度数
d[s[1]]++;
}
int cnt=0;
for(int i=64; i<130; i++ ) {
if ( d[i] && i==find(i) ) cnt++; //记录根的个数
}
if ( cnt!=1 ) { //超过一个根就不是连通图了,第一个步骤
printf("No Solution\n"); return 0;
}
int head=0;
cnt=0;
for(int i=64; i<130; i++ ) {
if ( d[i]%2 ) { //记录奇点个数
cnt++;
if ( !head ) head=i; //记录第一个,这样就是最小字符起头了
}
}
if ( cnt>0 && cnt!=2 ) { //奇点个数不是0或者2,第二个步骤
printf("No Solution\n"); return 0;
}
if ( !head ) { //如果是欧拉回路需要找到第一个头
for(int i=64; i<130; i++ ) {
if ( d[i] ) { head=i; break;}
}
}
ans[n+1]='\0';
dfs(head); //dfs深搜答案了,第三个步骤
printf("%s\n",ans);
return 0;
}
有n个单词,若单词的首字母等于另一个单词的尾字母,就可以连起来,判断所给单词是否能够连起来,能的话输出字典序最小的词链,不能输出"***".
范围:n<1000
6
aloha
arachnid
dog
gopher
rat
tiger
aloha.arachnid.dog.gopher.rat.tiger
这一题范围很小,直接dfs。
有几个点需要注意:
1、比较困难的点是寻找第一个单词,接下来就是方法:
通过举例来理解
有三个字符串 acm,mo,orz.我们可以发现‘m’和‘o’都是可以对应的,再看看样例,就会有以下发现:第一个单词的首字母的数目,永远比所有单词的最后一个相同字母的数目多一(我知道有点难理解)。举个例子:样例中第一个单词的首字母’a’的出现次数是2次,而所有单词中末尾字母’a’出现的次数只有一次,而其它的字母都一一对应的出现了1次!
那就用两个数组来存储就行了。
#include
using namespace std;
const int N=1010;
string str[N];//输入
int s1[130],s2[130];//s1存储首字母个数,s2存储末尾字母个数
bool v[N]={0};//判重,dfs防止遍历相同的单词
bool flag=0;//用来判断是否完成dfs的
string ans[N];//记录答案
int n;
void dfs(int p, int num) {//p表示pos,现在记录单词的位置,num记录第几个单词
if ( flag ) return;
if ( num==n ) {//找到最后一个,存完就结束搜索了
ans[num]=str[p];
v[p]=1;
flag=true;
return;
}
v[p]=1;//对存入的标记,避免再次遍历
ans[num]=str[p];//记录答案
for(int i=0; i<n; i++ ) {
if ( v[i] ) continue;
int len=str[p].length();
if ( str[p][len-1]==str[i][0] ) {//两个单词头尾之间字母是否一样
dfs(i,num+1);
if (flag) return;
v[i]=0;//没找到回溯
}
}
}
int main() {
scanf("%d",&n);
for(int i=0; i<n; i++ ) {
cin>>str[i];
int len=str[i].length();
s1[str[i][0]]++;//记录首字母
s2[str[i][len-1]]++;//记录末字母
}
sort(str,str+n);//因为要按照最小的字典序输出,所以要sort一下
int start=0;//标记第一个字符串的位置
for(int i=0; i<n; i++ ) {
//下面的这个if就是注意点了,对首字母(s1)和尾字母(s2)的查找找到第一个单词
if ( s1[str[i][0]] && s2[str[i][0]] && s1[str[i][0]]-s2[str[i][0]]==1 ) {
start=i;
break;
}
}
dfs(start,1);//深搜答案
if (!flag) printf("***\n");
else {
for(int i=1; i<=n; i++ ) {
if ( i!=n ) cout<<ans[i]<<".";
else cout<<ans[i]<<endl;
}
}
return 0;
}
一个由n个点m条边构成的无向图,每个点被路障(有两种类型的路障)进行封锁,对封锁有以下要求
相邻的两点封锁的路障不能相同
不可以输出Impossible 可以的话输出最少的路障数
3 3
1 2
1 3
2 3
Impossible
3 2
1 2
2 3
1
1、记录每个点的路障种类,若这个点已有路障且路障种类不同,那就是错的。
2、记录两种路障所需要的数目。
3、因为可能出现多个地图,所以要遍历一遍,但对已经遍历过的点就不用重复了
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int v[N]={0};//判断是否访问过.同时用来记录路障,一种是1,一种是-1
int sum[2];//统计数目
vector<int> q[N];//建图
bool dfs(int x, int color) { //标色
if ( v[x] ) {
if ( v[x]!=color ) return false;//(思路1)
return true;
}
v[x]=color;
color==1?sum[1]++:sum[0]++;//记录路障种类数(思路2)
bool flag=true;
for(int i=0; i<q[x].size() ; i++ ) {
if( !flag ) break;
flag=dfs(q[x][i],-color);
}
return flag;
}
int main() {
int n,m;
scanf("%d %d",&n,&m);
for(int i=0; i<m; i++ ) {//建图
int x,y;
scanf("%d %d",&x,&y);
q[x].push_back(y);
q[y].push_back(x);
}
int ans=0;
for(int i=1; i<=n; i++ ) { //可能存在多个图 (思路3)
if ( v[i] ) continue; //已经遍历过的就跳过
sum[0]=sum[1]=0;
if ( !dfs(i,1) ) {
printf("Impossible\n");
return 0;
}
ans+=min(sum[0],sum[1]);
}
printf("%d\n",ans);
return 0;
}
有 n个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti的同学。游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自 己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入:n个人,每个人告诉的人,n<200000
5
2 4 2 3 1
3
这题建模来看就是求最小环,可以用并查集也可以用dfs
dfs思路:到过一个环的点记录步数,如果再次走到这个点上,就可以判断出成环,当前步数减去刚才到该点步数就是这个环长
#include
using namespace std;
#define INF 0x3f3f3f3f
const int N=200000+10;
int a[N],num[N];//a记录指向点,num记录到达该点需要的长度
bool v[N];//防止再次遍历
int ans;
void dfs(int x, int s) {//x表示所在点,s表示到这边需要的步数
if ( v[x] ) return;
if ( num[x] ) ans=min(ans,s-num[x]);
else {
num[x]=s;//先记录
dfs(a[x],s+1);
v[x]=1;//一定要后标记遍历
}
}
int main() {
int n;
scanf("%d",&n);
for(int i=1; i<=n; i++ ) scanf("%d",&a[i]);
ans=INF;
for(int i=1; i<=n; i++ ) dfs(i,0); //数字为步数,多个环遍历
printf("%d\n",ans);
return 0;
}
K(1≤K≤100)只奶牛分散在N(1≤N≤1000)个牧场.现在她们要集中起来进餐.牧场之间有M(1≤M≤10000)条有向路连接,而且不存在起点和终点相同的有向路.她们进餐的地点必须是所有奶牛都可到达的地方.那么,有多少这样的牧场呢?
2 4 4
2
3
1 2
1 4
2 3
3 4
2
如果对每个点都遍历过去的话,回T。其实只要对牛所在的点进行遍历就行了,每次遍历到的点在那个地方的num++,最后统计几个地方数量相等就好,简单题
#include
using namespace std;
const int N=1010;
vector<int> q[N];
int num[N];//记录每块地方牛的数量
bool v[N];//每次都要进行判断
int a[N]={0};
void dfs(int p) {
v[p]=1;
num[p]++;
for(int i=0; i<q[p].size(); i++ ) {
if ( !v[q[p][i]] ) dfs(q[p][i]);
}
}
int main() {
int k,n,m;
cin>>k>>n>>m;
for(int i=1; i<=k; i++ ) {
cin>>a[i];
}
for(int i=1; i<=m; i++ ) {
int x,y;
cin>>x>>y;
q[x].push_back(y);
}
for(int i=1; i<=k; i++ ) {
for(int j=1; j<=n; j++ ) v[j]=0; //避免无向图点的出现,不然要MLE的
dfs(a[i]);
}
int cnt=0;
for(int i=1; i<=n; i++ ) {
if ( num[i]==k ) cnt++;
}
cout<<cnt<<endl;
return 0;
}
幻象迷宫可以认为是无限大的,不过它由若干个N*M的矩阵重复组成。矩阵中有的地方是道路,用’.‘表示;有的地方是墙,用’#‘表示。LHX和WD所在的位置用’S’表示。也就是对于迷宫中的一个点(x,y),如果(x mod n,y mod m)是’.‘或者’S’,那么这个地方是道路;如果(x mod n,y mod m)是’#’,那么这个地方是墙。LHX和WD可以向上下左右四个方向移动,当然不能移动到墙上。
请你告诉LHX和WD,它们能否走出幻象迷宫(如果它们能走到距离起点无限远处,就认为能走出去)。如果不能的话,LHX就只好启动城堡的毁灭程序了……当然不到万不得已,他不想这么做。。。
多组输入输出,n,m小于1500
5 4
##.#
##S#
#..#
#.##
#..#
5 4
##.#
##S#
#..#
..#.
#.##
Yes
No
因为是走到无穷远的,x,y取模表示走到无穷远相同地图的位置。判断一下这个点是否走到过,且此时的坐标是否相等,如果相等就代表还是在同一张地图里,如果不相等就说明走到了这个点的无穷远。
#include
using namespace std;
const int N=1500+10;
const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//四个方向行走
int n,m;
int st_x,st_y;//起始位置
int vis[N][N][3];//第一维记录有无被访问,第二维记录被访问时横坐标,第三维纵坐标
bool fl,a[N][N];//a用来建立地图 ,1为墙0为路 ;fl用来判断能否走到无穷远
char ch;
void dfs(int x, int y, int lx, int ly) {
//x,y为取模的坐标,没有取模的坐标lx,ly
//第一次走这个迷宫,x=lx,y=ly;只要走到的一个点,x!=lx||y!=ly,
//那么这个点走了两遍
if( fl ) return;//可以走到无穷远了
if ( vis[x][y][0] && ( vis[x][y][1]!=lx || vis[x][y][2]!=ly ) ) {
fl=1;
return;
}
vis[x][y][1]=lx,vis[x][y][2]=ly,vis[x][y][0]=1;
for(int i=0; i<4; i++ ) {
int xx=(x+dir[i][0]+n)%n,yy=(y+dir[i][1]+m)%m;
int lxx=lx+dir[i][0],lyy=ly+dir[i][1];
if ( !a[xx][yy] ) {
if ( vis[xx][yy][1]!=lxx || vis[xx][yy][2]!=lyy || !vis[xx][yy][0] )
dfs(xx,yy,lxx,lyy);
}
}
}
int main() {
ios::sync_with_stdio(false);
while(cin>>n>>m) {
fl=0;
memset(a,0,sizeof(a));
memset(vis,0,sizeof(vis));
for(int i=0; i<n; i++ ) {
for(int j=0; j<m; j++ ) {
cin>>ch;
if ( ch=='#' ) a[i][j]=1;
if ( ch=='S' ) st_x=i,st_y=j;
}
}
dfs(st_x,st_y,st_x,st_y);
if ( fl ) puts("Yes");
else puts("No");
}
}