码迷,mamicode.com
首页 > 其他好文 > 详细

[CQOI2012] 组装 - 贪心

时间:2020-04-07 20:05:57      阅读:32      评论:0      收藏:0      [点我收藏+]

标签:ack   return   二次   它的   cpp   efi   abs   printf   code   

数轴上有 \(n\) 种点,总共有 \(m\) 个,每个点有它的坐标 \(x_i\) 和种类 \(p_i\)。求一个点,使得所有种类点中与这个点的最小距离的平方和最小。\(n \le 10^4, m \le 10^5, x_i \le 10^5\)

Solution

要最小化 \(\sum_{i=1}^n (x-x_i)^2\),显然在选定了每个种类使用的点以后,这是一个关于 \(x\) 的二次函数,其最小值为

\[\sum_{i=1}^n x_i^2 - \frac 1 n \sum_{i=1}^n x_i \]

于是现在我们只需要考虑如何选择每个种类使用的点

对于同一种点按坐标从小到大排序,每次枚举把某种点替换成他的下一个

由于原式取得最小值的条件是 \(x= \frac 1 n \sum_{i=1}^n x_i\)

我们将每次替换用一个二元组 \((x_i,x_i‘)\) 表示,那么我们只需要将所有二元组按照 \(x_i+x_i‘\) 排序,就一定不会错过最优解

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 100005;

int n,m,t1,t2,t3;
vector <int> g[N];
struct pii {
    int x,y;
};
vector <pii> p;
double s,s2,ans=1e18,tans=0;

signed main() {
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++) {
        cin>>t1>>t2;
        g[t2].push_back(t1);
    }
    for(int i=1;i<=n;i++) {
        sort(g[i].begin(),g[i].end());
    }
    for(int i=1;i<=n;i++) {
        if(g[i].size()) {
            s+=g[i][0];
            s2+=g[i][0]*g[i][0];
        }
        for(int j=1;j<g[i].size();j++) {
            p.push_back({g[i][j-1],g[i][j]});
        }
    }
    sort(p.begin(),p.end(),[](pii a,pii b)->bool{return a.x+a.y<b.x+b.y;});
    ans=min(ans,s2-s*s/n);
    tans=s/n;
    for(int i=0;i<p.size();i++) {
        s-=p[i].x;
        s2-=p[i].x*p[i].x;
        s+=p[i].y;
        s2+=p[i].y*p[i].y;
        ans=min(ans,s2-s*s/n);
        if(fabs(s2-s*s/n-ans)<1e-6) tans=s/n;
    }
    printf("%.4lf",tans);
}

[CQOI2012] 组装 - 贪心

标签:ack   return   二次   它的   cpp   efi   abs   printf   code   

原文地址:https://www.cnblogs.com/mollnn/p/12655394.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有 京ICP备13008772号-2
迷上了代码!