流言的传播 题解

这篇博客探讨了一道关于找到最小边集E,确保任何非全集点集S内仅有一条权值最小边的问题。作者首先介绍了30%数据的解决方案——通过构建生成树进行枚举验证,接着分析了该方法的错误并提供了70%数据的贪心算法,包括算法2.1和2.2,其中算法2.2利用最小生成树解决。最后,对于100%的数据,提出了进一步优化算法三,通过哈希优化求解最小生成树,实现O(nlogn+m)的时间复杂度。

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

博客园同步

本人并没有找到本题链接,抱歉。(纯属个人练习题,非本人原创)因此把题目内容 暂时 存放于 洛谷私人题库 中。

原题链接

简要题意:

找到一个最小的边集 E E E,使得对任意一个 不等于全集 的点集 S S S,恰好只有一个顶点在 S S S 里的边中 权值最小的那一条 在边集 E E E 中。

很显然,我们需要对 a i → b i a_i \rightarrow b_i aibi 连一条权值为 T i T_i Ti 的边,建图。

算法一

对于 30 % 30 \% 30% 的数据, N ≤ 10 , M ≤ 20 N \leq 10,M \leq 20 N10,M20.

首先我们分析一下可以得到:答案肯定是 N − 1 N-1 N1 条边,即构成一棵 生成树 。(否则有点没有被包含,而 整个图是连通图,显然不满足)

所以,我们只需要枚举这 9 9 9 条边的选择方法,然后暴力扫一遍验证即可。

时间复杂度: O ( C M N − 1 × n ) O(C_M^{N-1} \times n) O(CMN1×n).

实际得分: 30 p t s 30pts 30pts.

粗略的计算一下: C M N − 1 × n = 20 ! 11 ! × 9 ! × 10 C_M^{N-1} \times n = \frac{20!}{11! \times 9!} \times 10 CMN1×n=11!×9!20!×10,大概是 1.6 × 1 0 5 × 10 = 1.6 × 1 0 6 1.6 \times 10^5 \times 10 = 1.6 \times 10^6 1.6×105×10=1.6×106,可以通过。

算法二

对于 70 % 70 \% 70% 的数据, N ≤ 100 N \leq 100 N100 M ≤ 1 0 3 M \leq 10^3 M103.
对于 100 % 100 \% 100% 的数据, N ≤ 1 0 4 N \leq 10^4 N104 M ≤ 1 0 5 M \leq 10^5 M105.

由于算法一的分析,我们得到 选择的边构成一棵生成树

下面给出一种贪心。

算法 2.1

既然题目说 “权值最小的那一条在边集 E E E 中”,那么枚举每个点权值最小的那条边将其删去,如果重复则不删,删到 n − 1 n-1 n1 条边为止。

时间复杂度: O ( n + m ) O(n+m) O(n+m).

期望得分: 100 p t s 100pts 100pts.

实际得分: 0 p t 0pt 0pt.

为什么会这样呢?肯定是我们的贪心出错了。

给出一组反例(其实类似于样例,把 2 2 2 3 3 3 换了一下):

4 4
1 3 2
4 2 4
2 3 3
1 2 1

按照这样的贪心方法,你的步骤是:

  1. 枚举 1 1 1,并删去 1 → 2 1 \rightarrow 2 12 这条边。

  2. 枚举 2 2 2,因为 1 → 2 1 \rightarrow 2 12 已经被删去,所以删去 2 → 3 2 \rightarrow 3 23 这条边。

  3. 枚举 3 3 3,只有 3 → 1 3 \rightarrow 1 31 可以删,所以删去。

发现删去了 4 − 1 = 3 4-1 = 3 41=3 条边,结束。

你发现哪里错了么?如果你把 1 → 2 , 2 → 3 , 3 → 1 1 \rightarrow 2,2 \rightarrow 3 ,3 \rightarrow 1 12,23,31 删去,那么 4 4 4权值最小的那一条在边集 E E E 是不满足的!

那你说,好啊,那我把最后一条 3 → 1 3 \rightarrow 1 31 判断一下,发现 3 3 3 已经被连,所以枚举 4 4 4,删去 4 → 2 4 \rightarrow 2 42 这条边。

还是错的!因为对于 3 3 3 这个点,它最小的边权是 3 → 1 3 \rightarrow 1 31,又没有被删除!

所以 整个贪心被 hack \text{hack} hack 掉,我们需要更严谨的贪心。

算法 2.2

对于 70 % 70 \% 70% 的数据, N ≤ 100 N \leq 100 N100 M ≤ 1 0 3 M \leq 10^3 M103.

既然我们不能一个个枚举,就去注意另一个性质。

由算法 1 1 1 的分析可以知道,边集 E E E 应当是原图的一棵生成树。

那么,一个思路就来了:求最小生成树。

为什么最小生成树是正确的?我们来回忆一下, kruskal \texttt{kruskal} kruskal最小生成树 的过程。

  1. 用并查集维护每个点所属连通块,将取出边权最小的一条边,将两个端点合并。

  2. 在剩下的边中选择一条 两端点属于不同连通块 且 有一端点已被选过边权最小 的边,然后合并,取边。

  3. 反复做 2 2 2 步骤 n − 1 n-1 n1 次即可得到 最小生成树。

那么显然的性质:每个点的它边权最小的那条边包含于最小生成树中。如果不然,则应当用边权最小的边替换之,非最小生成树,得证。

所以,求最小生成树的时候记录删边即可。

那么,我们只需要每次取出满足条件的最小边权的边,维护并查集即可。

时间复杂度: O ( n 2 + m ) O(n^2 + m) O(n2+m).(并查集的 α ≤ 4 \alpha \leq 4 α4 被忽略)

期望得分: 70 p t s 70pts 70pts.

实际得分: 70 p t s 70pts 70pts ~ 100 p t s 100pts 100pts.(取决于常数大小)

算法三

对于 100 % 100 \% 100% 的数据, N ≤ 1 0 4 N \leq 10^4 N104 M ≤ 1 0 5 M \leq 10^5 M105.

有了算法二的铺垫,我们只需要优化求 最小生成树 的过程。

显然,每次取出的边一定要满足 一个已经被选,一个未选,用哈希优化即可。(不需要用并查集了)

时间复杂度: O ( n log ⁡ n + m ) O(n \log n + m) O(nlogn+m).(排序多出 log ⁡ \log log

期望得分: 100 p t s 100pts 100pts

实际得分: 100 p t s 100pts 100pts.

注(细节):实际上我们只需要记录边的信息,不需要建图。

#include<bits/stdc++.h>
using namespace std;
 
const int N=2e5+1;
 
inline int read(){char ch=getchar();int f=1; while(!isdigit(ch)) {if(ch=='-')f=-f; ch=getchar();}
    int x=0;while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}
 
struct tree{
    int start,end; //端点
    int len; int xuhao; //边权,原来的序号
};
tree a[N];
int n,m,s=0,p=0;
bool h[N]; //哈希
int ans[N]; //记录答案
 
inline bool cmp(tree x,tree y){
    return x.len<y.len;
} //结构体排序方式

int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++) {
        a[i].start=read(); a[i].end=read();
        a[i].len=read(); a[i].xuhao=i;
    }
    sort(a+1,a+1+m,cmp); //存边排序
    /*s=a[1].len;*/ h[a[1].start]=h[a[1].end]=1;
    printf("%d\n",n-1); ans[++ans[0]]=a[1].xuhao; //取出第一条边
    for(int p=2;p<n;p++) //做 n-2 次
    for(int i=2;i<=m;i++)
        if(h[a[i].start]+h[a[i].end]==1) {
			ans[++ans[0]]=a[i].xuhao; //记录答案
            h[a[i].start]=h[a[i].end]=1;
            break;  //找到最小的,结束
        } sort(ans+1,ans+1+ans[0]); //排序,输出答案
    for(int i=1;i<=ans[0];i++) printf("%d\n",ans[i]); 
    return 0;
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值