luogu P2503

题目链接
在这里插入图片描述

【数据规模】

对于 40 % 40\% 的数据,保证有 m n 10 m \le n \le 10 , 2 m 6 2 \le m \le 6

对于 100 % 100\% 的数据,保证有 m n 20 m \le n \le 20 2 m 6 2 \le m \le 6

解法

模拟退火
首先考虑把均方差的式子划开,找出哪些项和分组的方案有关

先不管外面的根号,这显然和分组的方案无关,然后把里面那个式子拿出来,发现就是方差,然后按照方差的方式化简,可以发现和分组有关的方案只有每一组数的和的平方,而且这个东西,如果知道顺序,是可以直接dp的,所以模拟退火的主要任务就是求出合理的顺序,使得每一组数的和的平方尽量小.然后模拟退火交换两个数的相邻位置.

#include<bits/stdc++.h>
using namespace std;
const double delta=0.98;
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!(isdigit(c))&&(c!=EOF)){if(c=='-')f=-1;c=getchar();}
	while((isdigit(c))&&(c!=EOF)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,m,a[25];
double b[25];
int f[25][25],s[25];
#define pf(x) ((x)*(x))
double calc(int a[]){
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int k=0;k<i;k++)
			f[i][j]=min(f[i][j],f[k][j-1]+pf(s[i]-s[k]));
		}
	}
	//printf("%d\n",f[n][m]);
	return 1.0*f[n][m]-(double)pf(s[n])/m;
}
void SA(){
	double T=1e6;
	int dfn=0;
	double ans=1e9,res;
	int st=clock();
	while(++dfn){
		if(dfn%100==0)if(clock()-st>=0.7*CLOCKS_PER_SEC)break;
		int x=rand()%n+1,y=rand()%n+1;
		swap(a[x],a[y]);
		res=calc(a);
		//for(int i=1;i<=n;i++)printf("%d ",a[i]);
		//puts("");
		//printf("%.2lf\n",sqrt(res/m));
		if(res<ans||exp(ans-res)/T>(double)rand()/RAND_MAX);
		else swap(a[x],a[y]);
		ans=min(ans,res);
		T*=delta;
	}
	printf("%.2lf\n",sqrt(ans/m));
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	srand(20200113);
	SA();
	return 0;
}

后续

可以发现我们这里的模拟退火并未怎么用到针对的这个函数的性质,所以可以直接随机化序列,也可以通过

// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define re register
#define sqr(x) ((x)*(x))
using namespace std;

inline int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=(X<<3)+(X<<1)+c-'0',c=getchar();
    return X*w;
}

int n,m,sum;
int a[30];
int x[10];
double ans=1000000000.0;
double ave;

inline void calc() {
    memset(x,0,sizeof(x));
    for (re int i=1;i<=n;i++) {
        int p=1;
        for (re int j=1;j<=m;j++)
            if (x[j]<x[p]) p=j;
        x[p]+=a[i];
    }
    double now=0;
    for (re int i=1;i<=m;i++) now+=sqr(x[i]-ave);
    now/=(double)m;
    if (now<ans) ans=now;
}

int main() {
    n=read(),m=read();
    for (re int i=1;i<=n;i++) a[i]=read(),sum+=a[i];
    ave=(double)sum/m;
    for (re int i=1;i<=500000;i++) {
        random_shuffle(a+1,a+n+1);
        calc();
    }
    printf("%.2f\n",sqrt(ans));
    return 0;
}
发布了95 篇原创文章 · 获赞 9 · 访问量 3205

猜你喜欢

转载自blog.csdn.net/wmhtxdy/article/details/103958419
今日推荐