T1.P1119 灾后重建
思路:
本题的重点在于并非在每一个时刻,每一个节点都可以到达,所以应枚举目前所有可以到达的节点k,并以k为中转点进行更新。剩下就是floyd的模板
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m;
int a[N],f[N][N];//f[][]->邻接矩阵存边,a[]->修复完成时间
void floyd(int k)
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(f[i][j]>f[i][k]+f[j][k])
f[i][j]=f[j][i]=f[i][k]+f[j][k];
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
cin>>a[i];//输入每个村庄修好要的时间
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
f[i][j]=1e9;//先初始化为最大值(用INT_MAX会炸???)
for(int i=0;i<n;i++)
f[i][i]=0;//一个点到自己距离为0
int s1,s2,s3;
for(int i=1;i<=m;i++)
{
cin>>s1>>s2>>s3;
f[s1][s2]=f[s2][s1]=s3;//初始化边长(无向边,存两次)
}
int q;
cin>>q;
int now=0;//表示现在是第几天
for(int i=1;i<=q;i++)//处理询问
{
cin>>s1>>s2>>s3;//第s3天村s1到s2最短路
while(a[now]<=s3&&now<n)//模拟第s3天各条路修复情况
{
floyd(now);
now++;
}
if(a[s1]>s3||a[s2]>s3) cout<<-1<<endl;//村庄没建好
else
{
if(f[s1][s2]==1e9) cout<<-1<<endl;//两点不连通
else cout<<f[s1][s2]<<endl;
}
}
}
T2.P1636 Einstein学画画
思路:
一个简单的欧拉回路的模板,需要统计每个点的度数,如果每个点的度数都为偶数,那么就可以一笔画(因为每个点都有进有出),否则统计度数为奇数的点的个数(记为num)答案就是num/2(每次都把度数为奇数的点分别作为起点和终点)。(但是好难想)
代码:
#include<bits/stdc++.h>
using namespace std;
int mp[10005];//mp[i]:点i的度数
int n,m ,ans;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
mp[x]++;
mp[y]++;//联通的话两点度数都+1
}
for(int i=1;i<=n;i++)
{
if(mp[i]%2==1)//如果度数是奇数就要多一笔画完(实际就是这样至于为啥..)
ans++;
}
if(ans)
cout<<ans/2;//因为一次可以连两个点所以/2(注意:一个连通图只可能有偶数个奇点)
else cout<<1;//都是偶数度数就一笔画完啦(每个点都有进有出)
return 0;
}
T3.P2504 [HAOI2006]聪明的猴子
思路:
最小生成树的题,把两个点之间的距离算出来,一边Kruskal,记录下最大的边,和每只猴子的跳跃距离比较一下,如果跳跃距离大就ans++。因为跳跃距离比最长的边都大的话,那就比所有的都大了,然后那只猴子就可以跳到所有的树上了
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,k,ans=0;
double sum=-1;
int a[N][3],b[N],pre[N];
struct coordinate
{
int x,y;
double p;//p->长度
}z[N];
int cmp(coordinate a,coordinate b)//按边的长短从小到大排序
{
return a.p<b.p;
}
int find(int x)//找根节点
{
if(pre[x]==x)
return x;
return pre[x]=find(pre[x]);
}
int main()
{
cin>>m;
for(int i=1;i<=m;i++)
cin>>b[i];
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i][1]>>a[i][2];
for(int i=1;i<=n;i++)//建边
{
for(int j=1;j<=n;j++)
{
if(i!=j)
{
k++;
z[k].x=i;//记录这条边连的两个点
z[k].y=j;//
z[k].p=sqrt((a[i][1]-a[j][1])*(a[i][1]-a[j][1])+(a[i][2]-a[j][2])*(a[i][2]-a[j][2]));
}
}
}
int cnt=n;//记录树上有几条边
sort(z+1,z+k+1,cmp);
for(int i=1;i<=n;i++)
pre[i]=i;
for(int i=1;i<=k;i++)/**Kruskal**/
{
if(cnt==1) break;//表示所有的点都在树上
int s1=find(z[i].x),s2=find(z[i].y);//判断起点终点在哪棵树上
if(s1!=s2)//如果起点终点不在同个树就合并
{
pre[s1]=s2;
cnt--;
sum=z[i].p;
}
}
for(int i=1;i<=m;i++)
{
if(sum<=b[i])
ans++;
}
cout<<ans<<endl;
}
T4.P3958 [NOIP2017 提高组] 奶酪
思路:
如果两个洞相交(或相切),就把它们连入一个集合。
可以想象一个集合就是一条通道。我们只需要判断每一条通道是否存在元素与底部、顶部相连即可。如果都有,那么输出 Yes。这个可以使用并查集来解决。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int disjoint_set[1005];
long long x[N],y[N],z[N];
int up_number[N],down_number[N];//up_number记录与顶面相交的洞的序号,down_number记录与底面相交的洞的序号
int find(int x)//查找+路径压缩
{
if(x!=disjoint_set[x])
disjoint_set[x]=find(disjoint_set[x]);
return disjoint_set[x];
}
long long distance(long long x1,long long y1,long long z1,long long x2,long long y2,long long z2)
{
return(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2);
}//两点距离公式,注意这里算的是距离平方。
int main()
{
int t,n,h;
long long r;
cin>>t;
for(int i=1;i<=t;i++)
{
cin>>n>>h>>r;
int up=0,down=0;//up->跟顶面相交的洞个数,down->底
for(int j=1;j<=n;j++)
disjoint_set[j]=j;//并查集初始化
for(int j=1;j<=n;j++)/**主体**/
{
cin>>x[j]>>y[j]>>z[j];
if(z[j]+r>=h)//判断是否跟顶面相交
{
up++;
up_number[up]=j;
}
if(z[j]-r<=0)//判断是否跟底相交
{
down++;
down_number[down]=j;
}
for(int k=1;k<=j;k++)//枚举之前的洞是否跟这个洞相交,相交就合并
{
if((x[j]-x[k])*(x[j]-x[k])+(y[j]-y[k])*(y[j]-y[k])>4*r*r) continue;//防止爆ll特判(如果两维都超那就不需要考虑第三维)
else if(distance(x[j],y[j],z[j],x[k],y[k],z[k])<=4*r*r)
{
int a1=find(j);
int a2=find(k);
if(a1!=a2) disjoint_set[a1]=a2;
}
}
}
bool flag=false;
for(int j=1;j<=up;j++)//看每个并查集是否有洞链接上下面
{
for(int k=1;k<=down;k++)
{
if(find(up_number[j])==find(down_number[k]))//这样说明相通
{
flag=true;
break;
}
}
if(flag) break;
}
if(flag) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
T5.P8654 [蓝桥杯 2017 国 C] 合根植物
思路:
使用并查集维护所有连根植物。
首先记录每个植物的根植物,一开始初始化为植物自己。
接着,寻找每对合根植物的根植物,将其结合起来。
最后判断所有的植物里,有多少个植物的根植物是不同的,也就是找出并查集中父节点的个数即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int num,ans;
const int N=1e6+5;
int disjoint_set[N],depth[N];//并查集和深度
void init()
{
for(int i=1;i<=n;i++)
{
disjoint_set[i]=i;//初始化每个节点的父节点为本身
depth[i]=0;//深度
}
}
int find(int a)//查找这个集合最终父节点
{
if(disjoint_set[a]==a)
return a;
else
return disjoint_set[a]=find(disjoint_set[a]);
}
void merge(int a,int b)//合并
{
a=find(a);
b=find(b);//先找到两个集合的源头
if(a==b) return;//如果源头是同个节点说明属于同个集合
else if(depth[a]>depth[b])
{
disjoint_set[b]=a;//如果a集合深度大于b,把b挂到a上
}
else//这里包含两种情况,d[a]<d[b]&d[a]=d[b]
{
if(depth[a]==depth[b])//深度相等不妨把a挂b上
depth[b]++;
disjoint_set[a]=b;//d[a]<d[b]的话不执行if直接把a挂b上
}
}
int main()
{
cin>>m>>n;
n*=m;//n现在代表总格子数
cin>>num;
init();
for(int i=1;i<=num;i++)
{
int x,y;
cin>>x>>y;
merge(x,y);
}
for(int i=1;i<=n;i++)//对每个格子寻根
{
if(find(i)==i)
ans++;
}
cout<<ans;
}