HDU 5604 merge(set)

本文探讨了一种有趣的数学问题,即如何通过一系列操作使数集中的最小数和最大数相遇,并找出完成此操作所需的最短时间。文章提供了一个算法解决方案,通过合并不同数集并计算最小最大数相遇的最短时间。

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

Description
有N个数,分别为1~N,对于一个数集,我们定义一下问题:
假设一开始每一个数都在数轴对应位置上,然后我们要做的是要让最小的数和最大的数相遇,并求相遇最小时间。
我们这样操作这些数:
每次操作可以选择两个数轴上相邻数交换,多个操作可以同时进行,但不能对一个数同时进行两个交换操作。
交换时间就是两个数在数轴上的距离,因为每个数一个单位时间移动一个单位距离,一个交换操作中途两个数不能停下来,不能改变交换对象,直到两个数交换了位置。
当最小的数和最大的数走到了相邻位置时,他们相遇的时间就是距离/2,因为交换中途就会相遇。
相遇完后,数的位置恢复原状。
下面举一个例子:
1..4……11….16..19
我们要让1和19相遇
首先让1、4和16、19交换,他们可以同时进行,而且恰好同时结束了。用时3个单位,然后变成下面这样:
4..1……11….19..16
接下来有两种情况:
A方案:
先让1和11先交换,19不动,那么用时7个单位,然后变成下面这样:
4..11……1….19..16
再交换1和19,用时2.5个单位。
加起来是这样的:3+7+2.5=12.5
B方案:
先让11和19交换,1不动,那么用时五个单位,且变成下面这样:
4..1……19….11..16
再用1和19交换,所用的时间是3.5个单位。
加起来是这样的:3+5+3.5=11.5
很明显B方案更优。
所以最少用时是11.5。
看样子大家都读懂题意了。
那么现在问题是这样的:一开始有N个数集,第i个数集就有且仅有i这个元素。
那么N-1个操作:每次合并两个数集,并且要你求出这个数集里面最小最大数相遇最小时间。
怎么样,很有挑战性吧?快点来挑战吧~。
Input
第一行一个数T(T<=5)。表示数据组数。
第一行一个数N,含义上面有说。
接下来N-1行,每行两个不同的整数u,v
表示将u所在点集和v所在点集合并,这里保证u和v在两个不同的集合里头。
1≤N≤300000
Output
对于每组数据:
N-1行,输出每次合并,输出要求的答案,保留一位小数。
Sample Input
1
3
1 2
1 3
Sample Output
0.5
1.5
Solution
对于某一个集合时的最优方案即在其中间值附近的两个点上他们做最后一步移动,两个点分别为a[i]和a[i+1]则最后答案为max(a[i]-Min, Max-a[i+1])+(a[i]+a[j])/2(Min和Max为集合中数的最值),把集合放在set里 那么我们只要找到(Min+Max)/2附近的几个点计算一下取一下最小值即可
Code

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
#define maxn 333333
#define INF 0x3f3f3f3f
set<int>s[maxn];
set<int>::iterator it1,it2;
int T,n,f[maxn],num[maxn];
void Init(int n)
{
    for(int i=1;i<=n;i++)
        f[i]=i,num[i]=1,s[i].clear(),s[i].insert(i);
}
int find(int x)
{
    if(f[x]==x)return x;
    return f[x]=find(f[x]);
}
void solve(int a,int b)
{
    f[b]=a,num[a]+=num[b],num[b]=0;
    s[a].insert(s[b].begin(),s[b].end()),s[b].clear();
    it2=s[a].end();it2--;
    int l=*(s[a].begin()),r=*it2;
    int mid=(l+r)>>1;
    it1=it2=s[a].lower_bound(mid);
    it2++;
    double ans=INF;
    if(it2!=s[a].end())
    {
        int x=*it1,y=*it2;
        double temp=0.5*(x+y);
        ans=min(ans,max(r-temp,temp-l));
    }
    if(it1!=s[a].begin())
    {
        it1--,it2--;
        int x=*it1,y=*it2;
        double temp=0.5*(x+y);
        ans=min(ans,max(r-temp,temp-l));
    }
    if(it1!=s[a].begin())
    {
        it1--,it2--;
        int x=*it1,y=*it2;
        double temp=0.5*(x+y);
        ans=min(ans,max(r-temp,temp-l));
    }
    printf("%.1lf\n",ans);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        Init(n);
        for(int i=1;i<n;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            a=find(a),b=find(b);
            if(num[a]<num[b])swap(a,b);
            solve(a,b);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值