• 洛谷 P2503 [HAOI2006]均分数据(模拟退火,dp)


    传送门


    大难不死,必有后福
    自从在空间里大骂后
    image
    十分钟过了两道调了两天的题……

    解题思路

    非常经典的一个对序列进行模拟退火的题。
    求方差时直接用公式就行(平均数一开始可以预处理出来)
    求分组后最小的方差需要用到dp。
    dp[i][j]表示前i个数分j组的方差。
    直接枚举k转移即可。

    注意事项

    double类型的数组用memset初始化:
    0x7f--一个很大的数
    0x3f--一个很小的数(<1)

    AC代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<ctime>
    #include<iomanip>
    #include<cstdlib>
    using namespace std;
    const double delta=0.996;
    int n,m,a[25],d[25];
    double ans=1e18,suma,dp[25][25];
    double cal(){
    	memset(dp,0x7f,sizeof(dp));
    	dp[0][0]=0;
    	for(int i=1;i<=n;i++) d[i]=d[i-1]+a[i];
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=min(i,m);j++){
    			for(int k=0;k<i;k++){
    				dp[i][j]=min(dp[i][j],dp[k][j-1]+((d[i]-d[k])-suma/m)*((d[i]-d[k])-suma/m));
    			}
    		}
    	}
    	return dp[n][m];
    }
    void SA(){
    	ans=min(ans,cal());
    	double t=3000;
    	while(t>1e-15){
    		int x=rand()%n+1,y=rand()%n+1;
    		if(x==y) continue; 
    		swap(a[x],a[y]);
    		double res=cal();
    		double cha=res-ans;
    		if(cha<0) ans=res;
    		else{
    			if(exp(-cha/t)*RAND_MAX<rand()) swap(a[x],a[y]);
    		}
    		t*=delta;
    	}
    }
    int main(){
    	srand(time(0));
    	srand(rand());
    	srand(rand());
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),suma+=a[i];
    	while((double)clock()/CLOCKS_PER_SEC<0.8) SA();
    	printf("%.2lf",sqrt(ans/m));
    	return 0;
    }
    
  • 相关阅读:
    PAT之我要通过
    卡拉兹(Callatz)猜想
    数组元素循环右移问题
    Reorder List
    一个fork的面试题
    内存流和null字节
    标准C IO函数和 内核IO函数 效率(时间)比较
    由fdopen和fopen想到的
    VS经常报错的link error 2019
    VS快捷键设置
  • 原文地址:https://www.cnblogs.com/yinyuqin/p/15027285.html
Copyright © 2020-2023  润新知