P2503 [HAOI2006]均分数据

斯德哥尔摩

2018-07-12 21:58:40

Personal

[P2503 [HAOI2006]均分数据](https://www.luogu.org/problemnew/show/P2503) 题目要求:$$\sigma=\sqrt{\frac{\sum_{i=1}^m(x_i-\overline x)^2}{m}},\overline x=\frac{\sum_{i=1}^mx_i}{m}$$ 把那个式子单独提出来:$\sum_{i=1}^m(x_i-\overline x)^2$ 即:$\sum_{i=1}^m(x_i^2+\overline x^2-2x_i\overline x)$ 拆开:$\sum_{i=1}^mx_i^2+n\overline x^2-2\overline x\sum_{i=1}^mx_i$ 而我们知道,无论怎么分组,$\overline x$与$\sum_{i=1}^mx_i$都是不变的。 于是,问题就转化成了:求$\sum_{i=1}^mx_i^2$的最小值。 立马想起的是贪心与$DP$。。。 然而我们选择贪心。 为什么?因为$DP$的状态转移方程实在太难列,为了降低思维难度,选择贪心。 那么问题来了,如何贪心才能取到最优解? 于是,一种辅助贪心的算法横空出世——**模!拟!退!火!** 不知道的请戳[这](https://www.cnblogs.com/Yangrui-Blog/p/9296567.html)。 每次随机一个位置$i$,将这个位置的数字加到和最小的一组中,然后与当前最小值比较,更新即可。 这样,我们就可以极大概率的得到最优解。 注意:模拟退火的参数调试很烦人。。。 一个好的随机化种子界定了模拟退火的效率高低。。。 附代码: ```cpp #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> #define MAXN 25 using namespace std; int n,m; int belong[MAXN]; double average=0.00,minn=2147483645,val[MAXN],s[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } void SA(){ double T=200,ans=0.00; memset(s,0,sizeof(s)); for(int i=1;i<=n;i++){ belong[i]=rand()%m+1; s[belong[i]]+=val[i]; } for(int i=1;i<=m;i++)ans+=(s[i]-average)*(s[i]-average); while(T>1e-10){ double last=ans; int x=min_element(s+1,s+m+1)-s,y=rand()%n+1; ans-=(s[belong[y]]-average)*(s[belong[y]]-average)+(s[x]-average)*(s[x]-average); s[belong[y]]-=val[y];s[x]+=val[y]; ans+=(s[belong[y]]-average)*(s[belong[y]]-average)+(s[x]-average)*(s[x]-average); if(ans<last||exp((ans-last)/T)*RAND_MAX<rand())belong[y]=x; else{ ans=last; s[belong[y]]+=val[y];s[x]-=val[y]; } T*=0.99; } minn=min(minn,ans); } void work(){ for(int cases=1;cases<=1000;cases++)SA(); printf("%.2lf\n",sqrt(minn/(double)m)); } void init(){ srand(2002); n=read();m=read(); for(int i=1;i<=n;i++){ val[i]=read(); average+=val[i]; } average/=(double)m; } int main(){ init(); work(); return 0; } ```