6.1网线的长度
从1号点开始,进行 bfs 遍历,找到距离最大的点,即为直径的一个端点v1.
图论中, 图的直径是指任意两个顶点间距离的最大值.
(距离是两个点之间的所有路的长度的最小值)
从v1开始进行bfs遍历,找到距离最大的点,即为直径的一个端点v2;
将所有点距离 v1 的长度进行数组存储,将所有点距离 v2 的长度进行数组存储
取两个数组中对应的两个距离中的大值即可。
前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序
如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.
用sum[i]来记录所有以i为起点的边在数组中的存储长度.
用head[i]记录以i为边集在数组中的第一个存储位置.
其中edge[i].to表示第i条边的终点
edge[i].from表示第i条边的起点
edge[i].w为边权值
edge[i].next表示与第i条边同起点的下一条边的存储位置
另外还有一个数组head[],它是用来表示以i为起点的第一条边存储的位置
实际上你会发现这里的第一条边存储的位置其实,
在以i为起点的所有边的最后输入的那个编号.
head[]数组一般初始化为-1
实现过程
从某一个点开始(这里是0)遍历,寻找最大直径的一个端点v1
从所找到的最大端点v1开始,遍历寻找最大直径的另一个端点v2
用数字sum2、sum3统计图中的点到两个端点的距离
最后输出数组中两个值的最大值即可
寻找最大点的实现:通过加边实现距离的不断更新,进而实现最大端点的更新
#include<iostream>
using namespace std;
int v1,v2,count,len1,len2,len3;
int head[10010];
int sum1[10010],sum2[10010],sum3[10010];
bool flag1[10010],flag2[10010],flag3[10010];
struct graph//图结构
{
int from;
int to;
int w;
int next;
}e[20020];
void addedge(int from,int to,int w)//插入边
{
e[count].from=from;
e[count].to=to;
e[count].w=w;
e[count].next=head[from];
head[from]=count;
count++;
}
//遍历第一遍找谁是最大直径的一个端点v1
void dfs1(int u)
{
flag1[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
{
if(!flag1[e[i].to])
{
flag1[e[i].to]=1;
sum1[e[i].to]=sum1[u]+e[i].w;
if(sum1[e[i].to]>len1)
v1=e[i].to;
len1=max(len1,sum1[e[i].to]);
dfs1(e[i].to);
}
}
}
//遍地第二遍找谁是最大直径的第二个端点,同时确定第一个最大端点到所有点的距离
void dfs2(int u)
{
flag2[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
{
if(!flag2[e[i].to])
{
flag2[e[i].to]=1;
sum2[e[i].to]=sum2[u]+e[i].w;
if(sum2[e[i].to]>len2)
v2=e[i].to;
len2=max(len2,sum2[e[i].to]);
dfs2(e[i].to);
}
}
}
//遍历第三遍找第二个端点到其他点的距离
void dfs3(int u)
{
flag3[u]=1;
for(int i=head[u];i!=-1;i=e[i].next)
{
if(!flag3[e[i].to])
{
flag3[e[i].to]=1;
sum3[e[i].to]=sum3[u]+e[i].w;
dfs3(e[i].to);
}
}
}
int main()
{
int n;
while(cin>>n)
{
for(int i=0;i<10010;i++)
{
sum1[i]=0;
sum2[i]=0;
sum3[i]=0;
flag1[i]=0;
flag2[i]=0;
flag3[i]=0;
head[i]=-1;
}
len1=0;
len2=0;
len3=0;
count=0;
for(int i=2;i<=n;i++)
{
int v,w;
cin>>v>>w;
addedge(i,v,w);//无向图要插入双向
addedge(v,i,w);
}
dfs1(1);
dfs2(v1);
dfs3(v2);
for(int i=1;i<=n;i++)
{
cout<<max(sum2[i],sum3[i])<<endl;
}
}
}
6.2.病毒传播问题
该问题是统计一个点的间接与直接连接点的问题。
思路:将点连接,就是在点之间不断加边,最后输出对应点所在连通边上点的数目。
实现:发现两个点接触时,判断两点是否连通,若两个点不连通,对两点加边。
加边后,改变原一点的根节点,更新根节点所在边的点数目。
最终输出0号节点对应根节点所在边上点的数目即可。
#include<iostream>
#include<stdio.h>
using namespace std;
const int maxn=3e4+10;
int par[maxn];
int count[maxn];
int find(int x)//查找
{
return par[x]==x ? x:par[x]=find(par[x]);
}
bool unite(int x,int y)//合并的过程中对该点的所有连通点数目进行统计
{
x=find(x);y=find(y);
if(x==y) return false;
par[x]=y;
count[y]=count[y]+count[x]; //统计以y为根节点的数目
return true;
}
int main()
{
int n,m;
while(cin>>n>>m)//n是学生的数量,m是学生群体的数
{
if(n==0&&m==0)
{
return 0;
}
for(int i=0;i<n;i++)
{
par[i]=i;
count[i]=1;
}
for(int i=0;i<m;i++)
{
int num;//团体人员数量
int preson=n;
scanf("%d",&num);
for(int i=0;i<num;i++)
{
int id;//这个小团体的学生编号
scanf("%d",&id);
if(preson!=n) //只有当有两个人的时候,才可以进行合并
{
unite(id,preson);
}
preson=id;
}
}
printf("%d\n",count[find(0)]);//0连通的点的数目,放在0的根节点的统计数中
}
return 0;
}
6.3.魔法灌溉问题
构建0号区域,将1-n号田的魔法引水转化为0号区域引水
构建出0号区域后,我们就会得到一个n*(n+1)的矩阵
该矩阵的建立目的是实现了图的存储
该问题转化为了,在图中寻找最小连通路径
实现过程:采用kruskal算法
不断的选取图中的最小边,当边所连的两点未连通时,选择该边加入
并将该边的花费进行统计,
当所加边为(n-1)条时,证明n个点已经连通,此时选边终止
统计量ans即为灌溉所有田地的最小花费
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=3e2+10;
int k,n;
struct edge //水渠结构体数组
{
int u,v,w;
bool operator<(const edge &p)const//按照权值从小到大排序
{
return w<p.w;
}
}e[maxn*maxn];
int node[maxn]; //并查集的节点数组,值代表当前节点的父节点
//并查集的查询算法,目的是看两个点是否连通
int find( int a)
{
return node[a] == a ? a : find(node[a]);
}
//对两个节点合并
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return false;
node[x]=y;
return true;
}
int kruskal()//最小生成树
{
//初始化每个麦田所在树结构的根节点为自身,这里有 0号麦田
for(int i=0;i<=n;i++)
{
node[i] = i;
}
sort(e,e+k);//排序
int ans=0;
int count=0;
for(int i=0;i<k;i++)
{ //如果父节点不相同,说明该两个麦田不在一个树中,
//则把该两个节点合并到一个树中,即使父节点相同,
if(unite(e[i].u,e[i].v))
{
//建造这两个麦田之间的水渠,因为已经过排序,所以是当前花费最少的一个水渠
//如果两个麦田的根节点相同,说明这两个麦田以通水田,
//不需要再建立,则跳过该条水渠的建立
ans+=e[i].w; //增加花费
count++; //增加水渠数量
}
if(count==n)
{
return ans;
}
}
return ans;
}
int main()
{
//n代表麦田数量
scanf("%d",&n);
//输入每一条可建造的水渠的数据,此时输入时要建立0好原点
k=0;
for(int i=0;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&e[k].w);
if(i==j) continue;//pii=0
e[k].u=i;
e[k].v=j;
k++;
}
}
int num=kruskal();
printf("%d",num);
return 0;
}
6.4.数据中心问题
实际上是从最小生成树中,选取最大边
同6.3,不过此时ans不统计所有边的权重和,而是更新统计选取边中权重最大边的权重
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=50000+5;
const int M=100000+5;
struct edge
{
int u,v,w;
bool operator<(const edge &t)
{ return w<t.w;}
}e[M];
int n,m,root;
int par[N];
void init(int n)
{
for(int i=1;i<=n;i++)
{
par[i]=i;
}
}
int find(int x)
{
return par[x]==x?x:par[x]=find(par[x]);
}
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y){return false; }
par[x]=y;
return true;
}
int main()
{
scanf("%d%d%d",&n,&m,&root);
init(n);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
}
sort(e,e+m);
int cnt=0,ans=0;
for(int i=0;i<m;i++)
{
if(unite(e[i].u,e[i].v))
{
ans=max(ans,e[i].w);
cnt++;
if(cnt==n-1)
{
printf("%d\n",ans);
return 0;
}
}
}
printf("%d\n",-1);
return 0;
}
6.5.关于一手牌的判断与提交
选取5张牌:该问题的解法是通过深度优先搜索的剪枝策略实现
对于所定义的n张牌,处理每一张牌的时候
我们都有两种选择:选/不选该张牌,相当于对于一种选择直接选出一条路径
剪枝化策略:当选牌范围超出最大范围或选够了5张牌,这种路径终止
当遇见选牌为首先拿出的两张牌的时候,对该牌进行跳过
关于牌型的判断实现:
一张牌的特征点为:花色,大小
我们分别对牌面的大小与花色统计,我们初始化牌型为散牌,再进行判断更改
当牌面大小相同牌数最大为3时,判断上一个统计数
当统计数为2时,牌型为3带2(5);否则牌型为3条(7)
当牌面大小相同牌数最大为2时,判断上一个统计数
当统计数为2时,牌型为双对(6);否则牌型为单对(8)
当牌面大小相同牌数最大为4时,此时该牌型为炸弹(4)
判断顺子是通过循环判断5张牌的差值,来进行判断,是顺子则判断为(2)
对花色进行统计,判断同一花色的最大牌数,若为5,则为同花。
在顺子的基础上判断是否为同花,非顺子同花为(3)
顺子同花为(1)
#include<iostream>
#include<algorithm>
using namespace std;
int A,B; //A 代表牌面大小,B代表牌面花色
struct card
{
int a;
int b;
bool operator<(const card &p) const
{
if(a!=p.a) return a<p.a;
else return b<p.b;
}
};
card c[100]; //用于标记所有的牌
int flag[100]={0};
card five[5]; //用于表示选中的5张牌
void dfs(int n,int *count,int pai,card *five)
{
if(n==5)
{
int size[25]={0};
int color[4]={0};
for(int i=0;i<5;i++)
{
size[five[i].a]++;
color[five[i].b]++;
}
sort(size,size+25);
sort(color,color+4);
//判断并确定牌型,初始化为散牌:k=9
int k=9;
if(size[24]==3)
{
if(size[23]==2)
{
k=5; //判断是否为三带二
}
else k=7; //判断是否为三条
}
else if(size[24]==2)
{
if(size[23]==2)
{
k=6; //判断是否为双对
}
else k=8; //判断是否为一对
}
else if(size[24]==4)
{
k=4; //判断是否为炸弹
}
else //判断是否为顺子
{
card pfive[5];
for(int i=0;i<5;i++)
{
pfive[i].a=five[i].a;
pfive[i].b=five[i].b;
}
sort(pfive,pfive+5);
int shun=1;
for(int i=1;i<5;i++)
{
if(pfive[i].a-pfive[i-1].a!=1)
{
shun=0;
}
}
if(shun==1)
{
k=2;
}
}
if(color[3]==5) //在顺子的基础上判断是否为同花
{
if(k==2) k=1;
else k=3;
}
count[k]++;
return;
}
if(pai>=A*B) //当牌已经遍历到循环的最后时,返回
{
return;
}
if(flag[pai]==1) //当这张牌是初始的两张牌之一时,跳过取下一张
{
dfs(n,count,pai+1,five);
}
else
{
dfs(n,count,pai+1,five); //跳过当前牌取下一张进行组合
five[n]=c[pai]; //取当前牌进行组合
dfs(n+1,count,pai+1,five);
}
}
int main()
{
cin>>A>>B;
for(int i=0;i<B;i++)
{
for(int j=0;j<A;j++)
{
c[i*A+j].a=j;
c[i*A+j].b=i;
}
}
int a1,b1,a2,b2;
cin>>a1>>b1>>a2>>b2;
five[0]=c[b1*A+a1]; //对取出的两张牌进行存储
five[1]=c[b2*A+a2];
flag[b1*A+a1]=1; //对取出的两张牌进行标记
flag[b2*A+a2]=1;
int n=2; //用n标记当前取出牌的数目
int count[10]={0}; //用count进行牌型计数
dfs(n,count,0,five);
for(int i=1;i<=9;i++)
{
cout<<count[i]<<" ";
}
}