[GESP202606]C++八级编程题题解

写下此博客以纪念我糟糕的八级.

1-线网建设:

题目大意:

在坐标系上给定 \(n\) 个点,每个点坐标为 \({x_i,y_i}\) ,任意两个点连接的距离为两点间的##欧式距离##.

什么?你问什么是欧式距离?就是勾股定理的一个应用,公式为 \(\sqrt{(x_i - y_i)^2 + (y_i - y_j)^2}\).

那么,现在边被下了禁制,两点连边的距离,必须##小于等于##整数 \(l\) .

现在问你,这 \(n\) 个点处在一个连通块里所需的边##最小距离和##是多少?

如果不理解同一个连通块,那么就是在这一个图中(该图为无向图),一个点可以通过一条路径到达块中的任意一个节点.
如果到不了,那么相当于这个点不在这个连通块中,被孤立了(悲).

数据范围:对于所有数据,满足 \(1 \le n \le 500\) , \(1 \le l \le 100\) , \(-100 \le x_i,y_i \le 100\) .

解法:

连通块?最小边权?最小生成树?难道...

是的,对于这种求满足连通块所需的最小边权,最小生成树就是最好的解法.

为什么非得是树?
题目中的边权不会出现负数(因为距离不会有负数),既然只要保证连通,那么你已经联通了一个点,为什么还要通过别的点再次绕到这个点呢?这样就形成了环.

这与模板的不同,第一是边你要自己去加,你可以考虑全部的边都加进去,亦或是提前判定是否距离大于l,后续的处理便于模板大差不差.

代码:

#include<bits/stdc++.h>
using namespace std;
struct node{
    int u,v;
    double w;
}Edges[250005];
double ans;
int n,l,cnt,tot = 1,x[505],y[505],fa[505],b[205][205];
bool cmp(node a,node b){
    return a.w < b.w;
}
int find(int x){ //并查集寻找祖先
    if(fa[x] == x) return fa[x];
    else return fa[x] = find(fa[x]);
}
int main(){
    cin >> n >> l;
    for(int i = 1;i <= n;i++)
        cin >> x[i] >> y[i];
    // for(int i = 1;i <= n;i++){ //这是全枚举的情况
    //     for(int j = i + 1;j <= n;j++){
    //         Edges[tot].u = i;
    //         Edges[tot].v = j;
    //         Edges[tot].w = sqrt(pow((x[i] - x[j]),2) + pow((y[i] - y[j]),2));tot++;
    //     }
    // }
    for(int i = 1;i <= n;i++){
        for(int j = i + 1;j <= n;j++){
            int dx = x[i] - x[j],dy = y[i] - y[j];
            if(dx * dx + dy * dy > l * l) continue; //如果大于l则不加入
            Edges[tot].u = i,Edges[tot].v = j,Edges[tot].w = sqrt(dx * dx + dy * dy);tot++;
        }
    }
    sort(Edges + 1,Edges + tot,cmp);  //后续代码为kruskal模板代码
    for(int i = 1;i <= n;i++) fa[i] = i; //并查集
    for(int i = 1;i < tot;i++){ 
        int u = find(fa[Edges[i].u]);
        int v = find(fa[Edges[i].v]);
        if(u == v) continue;
        //if(Edges[i].w > l) break; 如果你在一开始加入了所有边,那么要判定是否距离大于l
        cnt++;fa[u] = v;
        ans += Edges[i].w;
    }
    if(cnt == n - 1) cout<<fixed<<setprecision(2)<<ans; //题目要输出小数点后两位
    else cout<<"Impossible";
    return 0;
}

2-堆石子:

题目大意:

给定 \(m\) 堆石子堆,其中第一堆石子必须放 \(n\) 个石子.之后后面的石子堆都必须严格小于前一个石子堆的石子数量.

比如第一堆放3个,那么第二堆要么放2个,要么放1个,必须小于3个.

此外,每一堆必须最少放一个石子,现在,请问有多少种石子摆放安排?答案对 \(10^9 + 7\) 取模.

注,如果两个方案中只要有一个石子堆摆放的数量不同,都记作不同的方案.

数据范围:所有数据满足 \(2 \le m \le 10^5\) , \(1 \le n \le 10^8\) .

解法:

我恨排列组合!!!

也许有人看到第一个石子堆固定会觉得变了,但实际上,我们完全可以抛弃掉这一个堆,因为无论任何组合,第一个石子堆必定是 \(n\) 个石子,因此不加入计算.

先说结论,后面的几个石子堆的方案计算就是从n-1个数中选m-1个数的组合数.

也许有人会问(我也想问):不是要严格下降吗?你这组合和严格下降一点关系都没有啊喂!!!

我确实也是这么想的...

但事实上,严格下降的序列也只是组合中的一种排列而已,也许这样有点混,那么举个例子:

现在假设:\({1,3,4}\).

1,3,4可以有哪些排列?有6种,那么,严格下降的有几种?只有一种,那就是 \({4,3,1}\) .

对于这三个数,你能举例出第二种满足严格下降的吗?并不能,而且选择的过程中每一个数都不一样.
因此,对于每一种组合,有且仅只有一种对应的严格下降的序列.

那么公式有了?\(n,m\) 那么大怎么求呢?

学过逆元的都知道,在 \(p\) 为质数的情况下,满足 \(a^b \equiv 1(mod\ p)\)\(b\) 可以为 \(a^{p - 2}\) .
那么对于组合公式中除法的情况无法取模,我们可以等价于乘分母的逆元来解决,并且,由于乘法同余的加持,是可以在过程中取模化简的.

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int n,m,ans = 1;
int qpow(int a,int b,int p){ //快速幂
    int res = 1;
    while(b){
        if(b & 1) res = (res * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return res;
}
int solve(int n,int m){
    if(m > n) return 0;
    for(int i = 0;i < m;i++){
        ans = (ans * (n - i)) % mod;
        ans = (ans * qpow(i + 1,mod - 2,mod)) % mod; //mod-2次方,求对应的逆元
    }
    return ans;
}
signed main(){
    cin >> m >> n;
    cout<<solve(n - 1,m - 1)<<'\n';
    return 0;
}

出题给我出好点的啊!!!

posted @ 2026-07-04 21:57  Cai_hy  阅读(56)  评论(0)    收藏  举报