平衡点 / 吊打XXX(洛谷-P1337)

题目描述

如图:有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

思路:

本题是一道物理题,根据物理学的知识,当一个系统处于平衡状态时,系统的总能量是最小的,根据图示我们可以判断,系统的总能量其实就是各个物体的重力势能和,而各个物体的质量是一定的,这就要求物体离桌子越远,离地越近

换句话说,也就是要找一个点 x,使得这个点到桌面上给出的 n 个点的距离与质量和最小,即:\sum_{i=1}^n m[i] * dis[i][x]

我们可以用模拟退火的方法来做这个题

首先,将 (0,0) 设为初始答案,然后不断选取一个坐标,比较选择该点时的代价与当前的代价:

  • 如果小于当前答案的代价,更新答案
  • 如果大于当前答案的代价,有一定概率更新答案

根据模拟退火算法,更新答案的概率,随着时间的增大与代价差的增大而减小,更新坐标的幅度同样随着时间的增大而减小

然后WA的话就多交几遍看 RP 了。。。

源代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
#define Pair pair<int,int>
LL quickPow(LL a,LL b){ LL res=1; while(b){if(b&1)res*=a; a*=a; b>>=1;} return res; }
LL multMod(LL a,LL b,LL mod){ a%=mod; b%=mod; LL res=0; while(b){if(b&1)res=(res+a)%mod; a=(a<<=1)%mod; b>>=1; } return res%mod;}
LL quickMultPowMod(LL a, LL b,LL mod){ LL res=1,k=a; while(b){if((b&1))res=multMod(res,k,mod)%mod; k=multMod(k,k,mod)%mod; b>>=1;} return res%mod;}
LL quickPowMod(LL a,LL b,LL mod){ LL res=1; while(b){if(b&1)res=(a*res)%mod; a=(a*a)%mod; b>>=1; } return res; }
LL getInv(LL a,LL mod){ return quickPowMod(a,mod-2,mod); }
LL GCD(LL x,LL y){ return !y?x:GCD(y,x%y); }
LL LCM(LL x,LL y){ return x/GCD(x,y)*y; }
const double EPS = 1E-15;
const int MOD = 1000000000+7;
const int N = 1000+5;
const int dx[] = {0,0,1,-1,1,1,-1,-1};
const int dy[] = {1,-1,0,0,1,-1,1,-1};
using namespace std;

struct Node{
    int x, y;
    int w;
} node[N];
int n;
double getDis(double x1,double y1,double x2,double y2){
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
double getRes(double x, double y) {//计算n个到选定(x,y)的距离乘以重量之和
    double res = 0.0;
    for (int i = 1; i <= n; i++)
        res += (double)getDis(x, y, node[i].x, node[i].y) * node[i].w;
    return res;
}
void SA(double &x, double &y) {
    double T = 3000;//初始温度
    double delta = 0.99;
    double res = getRes(x, y);//当前状态结果
    while (T > EPS) {
        double nx = x + (rand() * 2 - RAND_MAX) * T;//转移x状态
        double ny = y + (rand() * 2 - RAND_MAX) * T;//转移y状态
        double newRes = getRes(nx, ny);新状态下的结果
        double pr = exp((res - newRes) / T) * RAND_MAX;
        if (newRes < res || pr > rand()) {
            x = nx;//更新x状态
            y = ny;//更新y状态
            res = newRes;
        }
        T *= delta;
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d%d%d", &node[i].x, &node[i].y, &node[i].w);

    srand(time(0));
    srand(rand());
    srand(rand());
    double x = 0.0, y = 0.0;//初始状态
    for (int i = 1; i <= 10; i++)//模拟退火10次
        SA(x, y);
    printf("%.3lf %.3lf", x, y);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值