[CQOI2012]组装(贪心,数学)

传送门

题意:有n种零件,m个生产车间,给出每个车间的坐标xi和生产零件的种类pi,求组装车间的坐标,使得dis(1)+dis(2)+...+dis(n)最小,其中dis(x)表示生产第x种零件的生产车间到组装车间距离的平方的最小值.

分析:不难想到,我们最后的最优答案一定是从所有m 个生产车间中选出了n个,使得一个生产车间恰好生产一种零件,这样一定是最优的.(贪心策略)

设组装车间的坐标为x,选出的n个生产车间坐标为a1,a2...an,则有:

\(ans=\sum_{i=1}^n(x-a[i])^2\)

把完全平方式拆开可得:

\(ans=\sum n*x^2 - 2\sum_{i=1}^na[i]*x+\sum_{i=1}^n a[i]^2\)

根据上式,我们只要枚举x(组装车间),同时维护\(\sum_{i=1}^na[i]\)\(\sum_{i=1}^n a[i]^2\)这两个值就好了.

考虑如何枚举和维护?我们先假设组装车间x在所有生产车间的左边,则对于每种零件的生产车间,其对答案产生贡献的一定是最左边的那个生产车间.于是我们可以预处理出此时的答案,然后组装车间不断向右挪.

考虑组装车间不断向右挪的过程中,每种零件对答案产生贡献的生产车间如何变化?显然,对于同种零件的相邻两个生产车间,当组装车间越过它们的中点时,则对答案产生贡献的生产车间将会发生变化.

于是我们可以开一个结构体,按照同种零件相邻两个生产车间的中点坐标从小到大,存下转移前后的两个车间的坐标(这一小段,我知道我没有表述清楚,看不懂就直接看代码吧)

int n,m,tot;
long long x1,x2;//十年一场空,博主建议都开ll和db
double anspos,ans;
vector<int> b[20005];
struct battle{
    int x,p;
}a[200005];//记录生产车间坐标和生产零件种类的结构体
bool cmp(battle a,battle b){
    return a.x<b.x;
}
struct Battle{
    int Old,New;
    double mid;
}c[200005];//记录每一次转移的结构体
bool Cmp(Battle a,Battle b){
    return a.mid<b.mid;
}
void add(int Old,int New){
    c[++tot].Old=Old;
    c[tot].New=New;
    c[tot].mid=(Old+New)*1.0/2;
}
double Get(double x){return n*x*x-2*x1*x+x2;}
void turn(int Old,int New){
    x1-=Old;x1+=New;
    x2-=1LL*Old*Old;x2+=1LL*New*New;
}//类似于莫队的转移
int main(){
    n=read();m=read();//n种零件,m个生产车间
    for(int i=1;i<=m;i++){
        a[i].x=read();//坐标
        a[i].p=read();//生产零件的种类
    }
    sort(a+1,a+m+1,cmp);//按照坐标从小到大排序
    for(int i=1;i<=m;i++)
        b[a[i].p].push_back(a[i].x);
//此时每种零件的生产车间的坐标从小到大递增,存入数组b
    for(int i=1;i<=n;i++)
        for(int j=1;j<=b[i].size()-1;j++)
            add(b[i][j-1],b[i][j]);
//每一次存入同一种零件的相邻两个生产车间,并记录中点
    sort(c+1,c+tot+1,Cmp);//按照中点坐标从小到大
    for(int i=1;i<=n;i++){
        x1+=b[i][0];
        x2+=b[i][0]*b[i][0];
    }
//先假设组装车间在所有生产车间的左边
//所以计算每种零件最左边的生产车间对此时答案的贡献
    anspos=x1*1.0/n;ans=Get(anspos);
//anspos组装车间的坐标,等于所有生产车间坐标的平均值
//得到组装车间的坐标后,ans就可以直接用公式计算
    for(int i=1;i<=tot;i++){
        turn(c[i].Old,c[i].New);
        if(Get(x1*1.0/n)<ans){//更新最小值
            ans=Get(x1*1.0/n);
            anspos=x1*1.0/n;
        }
    }
    printf("%.4lf\n",anspos);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/PPXppx/p/10341554.html
今日推荐