P1337 [JSOI2004]平衡点 / 吊打XXX(模拟退火)

探讨了通过模拟退火算法解决绳结在多个重物作用下的平衡位置问题,详细介绍了算法实现过程,包括初始化、能量计算、温度调度等关键步骤。

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

(一)题面:

题目描述

如图:有n个重物,每个重物系在一条足够长的绳子上。每条绳子自上而下穿过桌面上的洞,然后系在一起。图中X处就是公共的绳结。假设绳子是完全弹性的(不会造成能量损失),桌子足够高(因而重物不会垂到地上),且忽略所有的摩擦。

问绳结X最终平衡于何处。

注意:桌面上的洞都比绳结X小得多,所以即使某个重物特别重,绳结X也不可能穿过桌面上的洞掉下来,最多是卡在某个洞口处。

输入输出格式

输入格式:

文件的第一行为一个正整数n(1≤n≤1000),表示重物和洞的数目。接下来的n行,每行是3个整数:Xi.Yi.Wi,分别表示第i个洞的坐标以及第 i个重物的重量。(-10000≤x,y≤10000, 0<w≤1000 )

 

输出格式:

你的程序必须输出两个浮点数(保留小数点后三位),分别表示处于最终平衡状态时绳结X的横坐标和纵坐标。两个数以一个空格隔开。

 

输入输出样例

输入样例#1:

3
0 0 1
0 2 1
1 1 1

输出样例#1: 

0.577 1.000

 

(二)题意:

(中文题详见题面)

 

(三)题解:

最终的系统稳定时,系统总的能量肯定最低,而总能量最低,就是各个物体的势能之和最低,那么也可以转化为桌面上的<连接各个物体的绳子的长度与其重量的乘积>之和最小。接下来就是非常类似于求费马了,只不过距离之和变成了<距离乘上一个权值>的和。

模拟退火不懂的可以先看看模拟退火简介

 

(四)代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#define eps 1e-14
#define delta 0.97
#define Random (T*(2*rand()-RAND_MAX))
#define Drand (long double)rand()/RAND_MAX
using namespace std;
const int maxn=1010;
struct node{
    double x,y,w;
    node(long double _x=0,long double _y=0){x=_x;y=_y;}
    void input(){scanf("%lf%lf%lf",&x,&y,&w);}
    node operator - (const node &n1)const{
        return node(x-n1.x,y-n1.y);
    }
    long double operator * (const node &n1)const{
        return x*n1.x+y*n1.y;
    }
}th[maxn],now,nxt,bst;
long double solve(node p,int n){
    long double res=0;
    for(int i=0;i<n;i++){
        res+=sqrt((p-th[i])*(p-th[i]))*th[i].w;
    }
    return res;
}
int main(){
    #ifdef DanDan
    freopen("in.txt","r",stdin);
    #endif // DanDan
    srand(time(0));
    int n;scanf("%d",&n);
    for(int i=0;i<n;i++){
        th[i].input();
        now.x+=th[i].x;
        now.y+=th[i].y;
    }now.x/=n,now.y/=n;
    long double ans=solve(now,n),bt=1e18;
    for(long double T=100000;T>eps;T*=delta){
            nxt.x=now.x+Random;
            nxt.y=now.y+Random;
            double res=solve(nxt,n);
            if(bt>res)bt=res,bst=nxt;
            if(ans>res||exp((ans-res)/T)>Drand)ans=res,now=nxt;
    }
    printf("%.3f %.3f\n",bst.x,bst.y);
    return 0;
}

 

(五)总结:

模拟退火练手题(虽然正解不是模拟退火的说~)。

### JSOI2008 星球大战 题目描述 在一个遥远的星系,一个黑暗的帝国依靠其超级武器统治着整个星系。为了反抗帝国的暴政,联盟计划通过一系列行动来削弱帝国的力量。在这个过程中,联盟会逐步摧毁一些星球上的基地,从而影响到这些星球之间的通信连接。 初始状态下,所有的星球都是相互连通的。每次攻击将会摧毁一颗星球及其上所有与其他星球的连线。给定星球的数量以及被摧毁星球的顺序,计算每一次攻击之后剩余星球间的联通分量数量[^2]。 ### 解法概述 此问题可以通过逆向思维加并查集的数据结构解决。具体来说: - 将输入数据倒序处理,即假设最后被摧毁的星球最先恢复。 - 使用并查集维护当前存在的星球集合及其连通关系。 - 对于每一个新加入(实际上是旧删除)的星球,检查它原本相邻的所有其他星球是否已经存在于当前图中;如果存在,则将它们所在的两个不同集合合并起来。 - 记录下每次操作后的连通域数目变化情况,并最终按照时间轴反向输出结果。 这种方法能够有效地追踪随着星球逐渐消失而导致的变化过程中的连通区域数量改变状况[^3]。 ```python def find(parent, i): if parent[i] != i: parent[i] = find(parent, parent[i]) return parent[i] def union(parent, rank, x, y): rootX = find(parent, x) rootY = find(parent, y) if rootX != rootY: if rank[rootX] > rank[rootY]: parent[rootY] = rootX elif rank[rootX] < rank[rootY]: parent[rootX] = rootY else: parent[rootY] = rootX rank[rootX] += 1 n, m = map(int, input().split()) edges = [] for _ in range(m): u, v = map(int, input().split()) edges.append((u - 1, v - 1)) destroy_order = list(map(lambda x: int(x) - 1, input().split())) parent = [i for i in range(n)] rank = [0] * n connected_components = n result = [] # Reverse process of destruction as restoration. restore_order = destroy_order[::-1] restored_planets = set() for planet_to_restore in restore_order: result.append(connected_components) restored_planets.add(planet_to_restore) for edge in edges: if planet_to_restore in edge and all(p in restored_planets for p in edge): connected_components -= (find(parent, edge[0]) != find(parent, edge[1])) union(parent, rank, edge[0], edge[1]) print("\n".join(str(cc) for cc in reversed(result))) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值