T1:P1636 Einstein学画画
题目描述
Einstein 学起了画画。
此人比较懒\~\~,他希望用最少的笔画画出一张画……
给定一个无向图,包含 $n$ 个顶点(编号 $1 \sim n$),$m$ 条边,求最少用多少笔可以画出图中所有的边。
## 输入格式
第一行两个整数 $n, m$。
接下来 $m$ 行,每行两个数 $a, b$($a \ne b$),表示 $a, b$ 两点之间有一条边相连。
一条边不会被描述多次。
## 输出格式
一个数,即问题的答案。
## 样例 #1
### 样例输入 #1
```
5 5
2 3
2 4
2 5
3 4
4 5
### 样例输出 #1
1
思路:
欧拉路:
- 定义:(非常契合题目一笔画完的要求)
若从起点到终点的路径恰通过图中每条边一次(起点与终点是不同的点),
则该路径称为欧拉路
- 定理:
存在欧拉路的条件:图是连通的,且存在2个奇点。 如果存在2个奇点,则欧拉路一定是从一个奇点出发,以另一个奇点结束。
题目由求多少笔画完这个图转化为求有多少条欧拉路,进而转化为求有多少对奇点,一对奇点对应一笔。(需要特判一下奇点为0时情况)
#include<bits/stdc++.h>
using namespace std;
int n,m,cnt[10005],ans;
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
cnt[x]++;cnt[y]++;//记录顶点的度数
}
for(int i=1;i<=n;i++){
if(cnt[i]%2)ans++;//记录奇点个数
}
if(ans==0)cout<<1<<endl;
else cout<<ans/2<<endl;
return 0;
}
T2:P8654 [蓝桥杯 2017 国 C] 合根植物
题目
w 星球的一个种植园,被分成 m \times nm×n 个小格子(东西方向 mm 行,南北方向 nn 列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?
输入格式
第一行,两个整数 m,n,用空格分开,表示格子的行数、列数(1<m,n<1000。1<m,n<1000)。
接下来一行,一个整数 k,表示下面还有 k 行数据 (0<k<10^5)(0<k<10^5)。
接下来 kk行,第行两个整数 a,b,表示编号为 a 的小格子和编号为 b 的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。
比如: 5×4 的小格子,编号:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
输出格式
一行一个整数,表示答案
输入输出样例
输入 #1复制
5 4 16 2 3 1 5 5 9 4 8 7 8 9 10 10 11 11 12 10 14 12 16 14 18 17 18 15 19 19 20 9 13 13 17
输出 #1复制
5
说明/提示
样例解释
思路:
有向图中查询集合个数,使用并查集的查询和合并功能,加上一个vis数组即可计数
#include<bits/stdc++.h>
using namespace std;
int n,m,k,ans;
int fa[1000005],vis[1000005];
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}//并查集查找功能
void union_xy(int x,int y){//将y所在集合合并到x所在集合
x=find(x);y=find(y);
fa[y]=x;
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n*m;i++)fa[i]=i;//初始化父节点
for(int i=1;i<=k;i++){
int x,y;
cin>>x>>y;
union_xy(x,y);
}
for(int i=1;i<=m*n;i++){
int p=find(i);
if(vis[p])continue;
vis[p]=1;ans++;
}
cout<<ans<<endl;
return 0;
}
T3:奶酪
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z = 0,奶酪的上表面为 z = h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑 到奶酪的上表面去?
输入格式
每个输入文件包含多组数据。
第一行,包含一个正整数 TT,代表该输入文件中所含的数据组数。
接下来是 TT 组数据,每组数据的格式如下: 第一行包含三个正整数 n,h,rn,h,r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的 nn 行,每行包含三个整数 x,y,zx,y,z,两个数之间以一个空格分开,表示空洞球心坐标为 (x,y,z)(x,y,z)。
输出格式
T 行,分别对应 T 组数据的答案,如果在第 ii组数据中,Jerry 能从下表面跑到上表面,则输出 Yes
,如果不能,则输出 No
。
输入输出样例
输入 #1复制
3 2 4 1 0 0 1 0 0 3 2 5 1 0 0 1 0 0 4 2 5 2 0 0 2 2 0 4
输出 #1复制
Yes No Yes
说明/提示
【输入输出样例 11 说明】
第一组数据,由奶酪的剖面图可见:
第一个空洞在 (0,0,0)(0,0,0) 与下表面相切;
第二个空洞在 (0,0,4)(0,0,4) 与上表面相切;
两个空洞在 (0,0,2)(0,0,2) 相切。
输出 Yes
。
第二组数据,由奶酪的剖面图可见:
两个空洞既不相交也不相切。
输出 No
。
第三组数据,由奶酪的剖面图可见:
两个空洞相交,且与上下表面相切或相交。
输出 Yes
。
思路:
- 题目大意化简:就是寻找一条从底面到顶面的路(只不过这个路是球连起来的)
- 判断球是否连起来(相切或相交):distance(球心1,球心2)<=2*r
- 然后用并查集连路径,然后查询是否有联通路(从底面到顶面)
#include<bits/stdc++.h>
using namespace std;
long long t,n,h,r;
int fa[1005],H1[1005],H2[1005],cnt1,cnt2;
/*fa【】用于并查集,H1[]记录与底面相连的球,,H2[]记录与顶面相连的球,cnt记录对应的数量*/
struct dot{
long long x,y,z;
}d[1005];//记录球心坐标
long long dist(dot a,dot b){
return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z);
}//计算距离
long long find(long long x){
if (x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}//并查集查找
void union_xy(long long x,long long y){//将x所在集合合并到y
x=find(x);y=find(y);
fa[x]=y;
}
int main()
{
cin>>t;
for(int i=1;i<=t;i++){
cin>>n>>h>>r;
cnt1=0;cnt2=0;
for(int j=1;j<=n;j++)fa[j]=j;//并查集初始化
for(int j=1;j<=n;j++){
cin>>d[j].x>>d[j].y>>d[j].z;
if(d[j].z-r<=0){//找到与底面相连的球
cnt1++;
H1[cnt1]=j;
}
if(d[j].z+r>=h){//找到与顶面相连的球
cnt2++;
H2[cnt2]=j;
}
for(int k=1;k<=j;k++){
dot a=d[j],b=d[k];
if(dist(d[j],d[k])<=4*r*r)union_xy(j,k);//两球相连合并路径
}
}
int flag=0;
for(int j=1;j<=cnt1;j++){
for(int k=1;k<=cnt2;k++){
if(find(H1[j])==find(H2[k])){
flag=1;
break;
}//查找有无从底面到顶面相连的路径
}
if(flag)break;
}
if(flag)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
T4:灾后重建
给出 B 地区的村庄数 NN,村庄编号从 00 到 N-1N−1,和所有 MM 条公路的长度,公路是双向的。并给出第 ii 个村庄重建完成的时间 t_iti,你可以认为是同时开始重建并在第 t_iti 天重建完成,并且在当天即可通车。若 t_iti 为 00 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 QQ 个询问 (x,y,t)(x,y,t),对于每个询问你要回答在第 tt 天,从村庄 xx 到村庄 yy 的最短路径长度为多少。如果无法找到从 xx 村庄到 yy 村庄的路径,经过若干个已重建完成的村庄,或者村庄 xx 或村庄 yy 在第 tt 天仍未重建完成,则需要返回 -1
。
输入格式
第一行包含两个正整数N,MN,M,表示了村庄的数目与公路的数量。
第二行包含NN个非负整数t_0, t_1,…, t_{N-1}t0,t1,…,tN−1,表示了每个村庄重建完成的时间,数据保证了t_0 ≤ t_1 ≤ … ≤ t_{N-1}t0≤t1≤…≤tN−1。
接下来MM行,每行33个非负整数i, j, wi,j,w,ww为不超过1000010000的正整数,表示了有一条连接村庄ii与村庄jj的道路,长度为ww,保证i≠ji=j,且对于任意一对村庄只会存在一条道路。
接下来一行也就是M+3M+3行包含一个正整数QQ,表示QQ个询问。
接下来QQ行,每行33个非负整数x, y, tx,y,t,询问在第tt天,从村庄xx到村庄yy的最短路径长度为多少,数据保证了tt是不下降的。
输出格式
共QQ行,对每一个询问(x, y, t)(x,y,t)输出对应的答案,即在第tt天,从村庄xx到村庄yy的最短路径长度为多少。如果在第t天无法找到从xx村庄到yy村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄yy在第tt天仍未修复完成,则输出-1−1。
输入输出样例
输入 #1复制
4 5 1 2 3 4 0 2 1 2 3 1 3 1 2 2 1 4 0 3 5 4 2 0 2 0 1 2 0 1 3 0 1 4
输出 #1复制
-1 -1 5 4
思路:
dij+堆优化,每次枚举k时都要判定在w时可不可以到这个点,并且看是否标记了k
#include <bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f;
const int N=210;
const int M=20010;
int f[N][N],vis[N];
int n,m,Q,t[N];
int u,v,w;
int main()
{
memset(f,INF,sizeof(f));
cin>>n>>m;
for(int i=0;i<n;i++) scanf("%d",&t[i]),f[i][i]=0;;
for(int i=1;i<=m;i++) cin>>u>>v>>w,f[u][v]=f[v][u]=w;
cin>>Q;
while(Q--)
{
scanf("%d%d%d",&u,&v,&w);
for(int k=0;k<n;k++)
{
if(t[k]<=w&&!vis[k])
{
vis[k]=1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(f[i][j]>f[i][k]+f[k][j])
f[i][j]=f[i][k]+f[k][j];
}
}
if (t[u]<=w&&t[v]<=w&&f[u][v]<INF) printf("%d\n",f[u][v]);
else printf("-1\n");
}
return 0;
}
T5:聪明的猴子
【问题】现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。
输入格式
输入文件monkey.in包括:
第1行为一个整数,表示猴子的个数M(2<=M<=500);
第2行为M个整数,依次表示猴子的最大跳跃距离(每个整数值在1--1000之间);
第3行为一个整数表示树的总棵数N(2<=N<=1000);
第4行至第N+3行为N棵树的坐标(横纵坐标均为整数,范围为:-1000--1000)。
(同一行的整数间用空格分开)
输出格式
输出文件monkey.out包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。
输入输出样例
输入 #1复制
4 1 2 3 4 6 0 0 1 0 1 2 -1 -1 -2 0 2 2
输出 #1复制
3
问题化简:求一颗最小生成树,然后看有多少只猴子能够在生成树上随意走动(大于生成树上的最大距离即可)。
思路:
- 第一步:建立边
- 第二步:Kruskal!!(依次将不能生成环的最短边加入集合)
- 第三步,算答案
#include<bits/stdc++.h>
using namespace std;
double tot=-1.0;
int M,h[5005],n,cnt,f[10005],ans;
struct dot{
int x,y;
}d[10005];//记录树坐标
struct way{
int x,y;//起点为x,终点为y,距离是z的路径
double z;
bool operator < (const way &a)const{
return z<a.z;
}
}w[1000005];//这个边数组要开得大一些,不然RE
int find(int x){
if(x==f[x])return x;
else return f[x]=find(f[x]);
}//并查集查找
void B_Init(int x){
for(int i=1;i<=x;i++)f[i]=i;
}
double dis(dot a,dot b);//计算距离
int main()
{
cin>>M;
for(int i=1;i<=M;i++)cin>>h[i];
cin>>n;
for(int i=1;i<=n;i++)cin>>d[i].x>>d[i].y;
for(int i=1;i<=n;i++){//The first step----建边
for(int j=1;j<=n;j++){
if(i==j)continue;
cnt++;
w[cnt].x=i;
w[cnt].y=j;
w[cnt].z=dis(d[i],d[j]);
}
}
sort(w+1,w+1+cnt);//优先最短的边//The second step---Kruskal
B_Init(n);//并查集初始化
int k=n;
for(int i=1;i<=cnt;i++){
if(k==1)break;
int x1=find(w[i].x),x2=find(w[i].y);
if(x1!=x2){
f[x1]=x2;//将起点所在集合,合并到终点所在集合
k--;
tot=max(tot,w[i].z);//寻找最大边权
}
}
for(int i=1;i<=M;i++){//The third step---算答案
if(h[i]>=tot)ans++;
}
cout<<ans<<endl;
return 0;
}
double dis(dot a,dot b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}