[HAOI2006]均分数据

题目链接

          经过了14次的提交,终于正大光明的把本题A掉了。。。。

算法:

          主要思想是模拟退火,具体操作就是,我们对于每一个生成的排列,用连续分组DP处理此情况下的最小均方差。

          dp[i][j]表示前i个数中分j组的最小均方差,dp[i][j]=min(dp[i][j],dp[k-1][j-1]+prefix[i]-prefix[j-1]-prefix[n]/m (1<=k<=i))

         (prefix是前缀和应该很显然吧。。。。。) 

Code:

#include
using namespace std;
#define rep(i,j,k) for(register int i=j;i<=k;i++)
int n,m;
int co[30];
template void chkmin(T &x,T y){x=x void read(T &num){
	char c=getchar();num=0;T f=1;
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){num=(num<<3)+(num<<1)+(c^48);c=getchar();}
	num*=f;
}

int temp1[30];int temp2[30];
double dp[30][30];double prefix[30];
inline double work(int a[]){
    memset(dp,127,sizeof(dp));
    rep(i,1,n){prefix[i]=prefix[i-1]+co[a[i]];}
    dp[0][0]=0;
    rep(i,1,n){
        rep(j,1,min(i,m)){
            rep(k,1,i){
                double evenbao=(prefix[i]-prefix[k-1]-prefix[n]*1.0/m);
                chkmin(dp[i][j],dp[k-1][j-1]+evenbao*evenbao);
            }
        }
    }
    return dp[n][m];
}
inline bool Okay(double x,double y){
    if(x>=0)return true;
    return rand()<=exp((-x)/y)*RAND_MAX;
}
inline double SA(){
    double temper=10000;
    random_shuffle(temp1+1,temp1+n+1);
    rep(i,1,n){temp2[i]=temp1[i];}
    double nop1=work(temp1);double re_value=nop1;
    
    while(temper>=0.01){
        int x=rand()%n+1;int y=rand()%n+1;
        if(x==y)continue;
        swap(temp2[x],temp2[y]);
        double nop2=work(temp2);chkmin(re_value,nop2);
        if(Okay(nop1-nop2,temper)){
            nop1=nop2;swap(temp1[x],temp1[y]);
        }else{
            swap(temp2[x],temp2[y]);
        }
        temper*=0.99;
    }
    return re_value;
}

int main(){
    srand(time(NULL));
    
    read(n);read(m);
    rep(i,1,n){read(co[i]);}
    rep(i,1,n){temp1[i]=i;}
    
    double ans=INT_MAX;
    rep(i,1,72){
        chkmin(ans,SA());
 	}
	double temp=m;
    printf("%.2f\n",(sqrt(ans/temp)));
    return 0;
}

 

你可能感兴趣的:(模拟退火,DP)