Week10

文章介绍了几道编程竞赛题目,包括Einstein如何用最少笔画出无向图的所有边,使用欧拉图和奇点理论解决;合根植物问题通过并查集来确定合根植物的数量;奶酪问题利用并查集判断空洞是否相通;灾后重建问题中应用Floyd算法求解最短路径;聪明的猴子问题则考察了猴子跳跃能力和树之间的连通性,可以使用Kruskal算法解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Einstein学画画

题目描述

Einstein 学起了画画。

此人比较懒\~\~,他希望用最少的笔画画出一张画……

给定一个无向图,包含 n 个顶点(编号 1 ~ n),m 条边,求最少用多少笔可以画出图中所有的边。

输入格式

第一行两个整数 n, m。

接下来 m 行,每行两个数 a, b(a 不等于 b),表示 a, b两点之间有一条边相连。

一条边不会被描述多次。

输出格式

一个数,即问题的答案。

样例 #1

样例输入 #1

5 5 2 3 2 4 2 5 3 4 4 5

样例输出 #1

1

提示

对于 50 % 的数据,n <=50,m <= 100。

对于 100%的数据,1 <= n <= 1000,1<= m <= 10^5。

解题思路:欧拉图,先求每一个点的度数,如果有奇点,则笔数等于(总奇点数/2),反之则可以一笔画

完整代码如下:

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;

int cnt[maxn];

int main()
{
    int n,m;
    cin>>n>>m;
    int x,y;
    for(int i=1; i<=m; i++){
        cin>>x>>y;
        cnt[x]++;
        cnt[y]++;
    }
    int sum=0;
    for(int i=1; i<=n; i++){
        if(cnt[i]%2==1) sum++;
    }
    if(sum==0) cout<<"1";
    else cout<<sum/2;
    return 0;
}

[蓝桥杯 2017 国 C] 合根植物

题目描述

w 星球的一个种植园,被分成 m * n 个小格子(东西方向 m 行,南北方向 n 列)。每个格子里种了一株合根植物。

这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入格式

第一行,两个整数 m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。

接下来一行,一个整数 k,表示下面还有 k 行数据 (0<k<10^5)。

接下来 k 行,第行两个整数 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

样例输入 #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

提示

样例解释

时限 1 秒, 256M。蓝桥杯 2017 年第八届国赛

解题思路:并查集

完整代码如下:

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+5;

int f[maxn],h[maxn];

int find(int i);
void merge(int a,int b);

int main()
{
    int m,n,k,cnt=1,ans=0;
    cin>>m>>n;
    for(int i=1; i<=m; i++){
        for(int j=1; j<=n; j++){
            f[cnt]=cnt;
            h[cnt]=0;
            cnt++;
        }
    }//父点初始化
    cin>>k;
    int a,b; 
    for(int i=1; i<=k; i++){
        cin>>a>>b;
        merge(a,b);//合并 
    }
    cnt=1;
    for(int i=1; i<=m; i++){
        for(int j=1; j<=n; j++){
            if(f[cnt]==cnt) ans++;
            cnt++;
        }//寻找有几个父根就有几个合株 
    }
    cout<<ans;
    return 0;
}

int find(int i) //寻找根节点 
{
    return f[i]==i?f[i]:f[i]=find(f[i]); 
}

void merge(int a,int b)
{
    int fa=find(a);
    int fb=find(b);
    if(fa==fb) return;
    if(h[fa]<h[fb]) f[fa]=fb;
    else{
        f[fb]=fa;
        if(h[fa]=h[fb]) h[fa]++;
    }
}

奶酪

题目背景

NOIP2017 提高组 D2T1

题目描述

现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z = 0,奶酪的上表面为 z = h。

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑 到奶酪的上表面去?

空间内两点 P1(x1,y1,z1)、P2(x2,y2,z_2) 的距离公式如下:

dist(P1,P2)=sqrt((x1−x2)^2+(y1−y2)^2+(z1−z2)^2)

输入格式

每个输入文件包含多组数据。

第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。

接下来是 T 组数据,每组数据的格式如下: 第一行包含三个正整数 n,h,r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。

接下来的 n 行,每行包含三个整数 x,y,z,两个数之间以一个空格分开,表示空洞球心坐标为 (x,y,z)。

输出格式

T 行,分别对应T 组数据的答案,如果在第 i 组数据中,Jerry 能从下表面跑到上表面,则输出 Yes,如果不能,则输出 No。

样例 #1

样例输入 #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

提示

【输入输出样例 1 说明】

第一组数据,由奶酪的剖面图可见:

第一个空洞在 (0,0,0) 与下表面相切;

第二个空洞在 (0,0,4) 与上表面相切;

两个空洞在 (0,0,2) 相切。

输出 Yes。

第二组数据,由奶酪的剖面图可见:

两个空洞既不相交也不相切。

输出 No。

第三组数据,由奶酪的剖面图可见:

两个空洞相交,且与上下表面相切或相交。

输出 Yes。

【数据规模与约定】

对于 20% 的数据,n = 1,1 <=h,r <= 10^4,坐标的绝对值不超过 10^4。

对于 40% 的数据,1 <= n <= 8,1 <= h,r <= 10^4,坐标的绝对值不超过 10^4。

对于 80% 的数据,1 <= n <= 10^3,1 <= h , r <= 10^4,坐标的绝对值不超过 10^4。

对于 100% 的数据,1 <= n <= 1*10^3,1 <= h , r <= 10^9,T <= 20,坐标的绝对值不超过 10^9。

解题思路:并查集

将相通的球成为一个集合,然后在每一个集合中找是否有与上下表面相通的球,

完整代码如下:

    #include <bits/stdc++.h>
    using namespace std;

    const int maxn=1e4+5;

    int t1,t2,f1[maxn],f2[maxn],f[maxn],n,h,r;

    struct circle{
    double x,y,z;
    }c[maxn];

    int dist(double x1,double y1,double z1,double x2,double y2,double z2);
    int find(int i);
    void merge(int a,int b);

    int main()
    {
    int t;
    cin>>t;
    while(t--){
        cin>>n>>h>>r;
        for(int i=1; i<=n; i++){
            cin>>c[i].x>>c[i].y>>c[i].z;
        }
        t1=0;
        t2=0;
        for(int i=1; i<=n; i++){
            f[i]=i;
        }//父点初始化 
        for(int i=1; i<=n; i++){  
            if(c[i].z<=r){
                t1++;
                f1[t1]=i;
            }//与下表面相通 
            if(c[i].z>=h-r){
                t2++;
                f2[t2]=i;
            }//与上表面相通
            for(int j=1; j<i; j++){
                if(dist(c[i].x,c[i].y,c[i].z,c[j].x,c[j].y,c[j].z)) merge(i,j); 
                else continue;
            }//两球相通合并 
        }
        int flag=0;
        for(int i=1; i<=t1; i++){
            for(int j=1; j<=t2; j++){
                if(find(f1[i])==find(f2[j])){
                    flag=1;
                    break;
                }//找到与上下表面相同的两球 
            }
            if(flag) break;
        }
        if(flag) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
    }

    int dist(double x1,double y1,double z1,double x2,double y2,double z2)//判断两球是否相通 
    {
        int flag=0;
        if (sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2))<=2*r) flag=1;
        return flag;
    }

    int find(int i)
    {
        return f[i]==i?f[i]:f[i]=find(f[i]);
    }

    void merge(int a,int b)
    {
        int fa=find(a);
        int fb=find(b);
        if(fa!=fb) f[fa]=fb;
    }   

灾后重建

题目背景

B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

给出 B 地区的村庄数 N,村庄编号从 0到 N-1,和所有 M 条公路的长度,公路是双向的。并给出第 i 个村庄重建完成的时间 ti,你可以认为是同时开始重建并在第 ti 天重建完成,并且在当天即可通车。若 t_i 为 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q个询问 (x,y,t),对于每个询问你要回答在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄y在第 t 天仍未重建完成,则需要返回 -1。

输入格式

第一行包含两个正整数N,M,表示了村庄的数目与公路的数量。

第二行包含N个非负整数t0, t1,…, t{N-1},表示了每个村庄重建完成的时间,数据保证了t0 ≤ t1 ≤ … ≤ t{N-1}。

接下来M行,每行3个非负整数i, j, w为不超过10000的正整数,表示了有一条连接村庄i与村庄j的道路,长度为w,保证i≠j,且对于任意一对村庄只会存在一条道路。

接下来一行也就是M+3行包含一个正整数Q,表示Q个询问。

接下来Q行,每行3个非负整数x, y, t,询问在第t天,从村庄x到村庄y的最短路径长度为多少,数据保证了t是不下降的。

输出格式

共Q行,对每一个询问(x, y, t)输出对应的答案,即在第t天,从村庄x到村庄y的最短路径长度为多少。如果在第t天无法找到从x村庄到y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄y在第t天仍未修复完成,则输出-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

提示

对于30%的数据,有N≤50;

对于30%的数据,有ti= 0,其中有20%的数据有ti = 0且N>50;

对于50%的数据,有Q≤100;

对于100%的数据,有N≤200,M≤N*(N-1)/2,Q≤50000,所有输入数据涉及整数均不超过100000。

解题思路:按照题意用邻接矩阵存图,因为重修时间和询问的时间都是递增的,所以用floyd算法进行最短路计算时,可以根据

的时间递增k(即中转的村庄)

完整代码如下:

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e4+5,inf=0x3f3f3f3f;

long long lu[maxn][maxn],a[maxn],n,m;

void floyd(int k);

int main()
{ 
    cin>>n>>m;
    for(int i=0; i<n; i++) cin>>a[i];
    int x,y,w;
    for(int i=0; i<n; i++) lu[i][i]=0;
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            lu[i][j]=inf;
        }
    }//初始化图 
    for(int i=1; i<=m; i++){
        cin>>x>>y>>w;
        lu[x][y]=lu[y][x]=w;
    }//邻接矩阵存图 
    int q,t,now=0;
    cin>>q;
    for(int i=1; i<=q; i++){
        cin>>x>>y>>t;
        while(a[now]<=t && now<n){
            floyd(now);
            now++;
        }//依次更新距离 
        if(a[x]>t || a[y]>t || lu[x][y]==inf) cout<<-1<<endl;
        else cout<<lu[x][y]<<endl;
    } 
    return 0;
}

void floyd(int k)
{
    for(int i=0; i<n; i++){
            for(int j=0; j<n; j++){
                lu[i][j]=min(lu[i][j],lu[i][k]+lu[k][j]);
            }
        }
}

[HAOI2006]聪明的猴子

题目描述

在一个热带雨林中生存着一群猴子,它们以树上的果子为生。昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上。猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面的不同树冠上来回穿梭,以找到喜欢吃的果实。

现在,在这个地区露出水面的有N棵树,假设每棵树本身的直径都很小,可以忽略不计。我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树的坐标都不相同)。

在这个地区住着的猴子有M个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。由于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。有的猴子跳跃的距离比较远(当然也可以跳到较近的树上),而有些猴子跳跃的距离就比较近。这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到对面的树上。

【问题】现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。

输入格式

输入文件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

样例输入 #1

4 1 2 3 4 6 0 0 1 0 1 2 -1 -1 -2 0 2 2

样例输出 #1

3

提示

【数据规模】

对于40%的数据,保证有2<=N <=100,1<=M<=100

对于全部的数据,保证有2<=N <= 1000,1<=M=500

解题思路:用邻接表存图,再加边根据权值从小到大排序,然后用kruskal进行连图,最后连上的就是最大边的最小值

对每一个猴子的跳跃距离与最大边进行比较,得出结果

完整代码如下:

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+5;

int f[maxn],mon[maxn],cnt;
double sum,ans;

struct node{
    int x,y;
}dis[maxn];

struct nod{
    int a,b;
    double s;
}z[maxn]; //存边  

bool cmp(nod t1,nod t2){
    return t1.s<t2.s;
}

int find(int i);
void merge(int a,int b,int i);

int main()
{
    int m;
    cin>>m;
    for(int i=1; i<=m; i++) cin>>mon[i];
    int n,k=0;
    cin>>n;
    for(int i=1; i<=n; i++) cin>>dis[i].x>>dis[i].y;
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++){
            if(i!=j){
                k++;
                z[k].a=i;
                z[k].b=j;
                z[k].s=sqrt((dis[i].x-dis[j].x)*(dis[i].x-dis[j].x)+(dis[i].y-dis[j].y)*(dis[i].y-dis[j].y));
            }
        }
    }//求边和边权,类似邻接表存图 
    //Kruskal
    sort(z+1,z+1+k,cmp);
    cnt=n;
    for(int i=1; i<=n; i++) f[i]=i;//并查集初始化 
    for(int i=1; i<=k; i++){
        if(cnt==1) break;  //当树只剩一棵未成图时 
        merge(z[i].a,z[i].b,i); //合并 
    }    
    for(int i=1; i<=m; i++){
        if(sum<=mon[i]) ans++;
    }
    cout<<ans<<endl;
    return 0;
}

int find(int i)
{
    return f[i]==i?f[i]:f[i]=find(f[i]);
}

void merge(int a,int b,int i)
{
    int fa=find(a);
    int fb=find(b);
    if(fa!=fb){
        f[fa]=fb;
        cnt--;
        sum=z[i].s;
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值