CMAGABC算法

`// ABC.cpp : Defines the entry point for the console application.
//自动测30个函数,输出加上成功率

include

include

include

include <stdlib.h>

include <math.h>

include <time.h>

include

include

using namespace std;

const int NP = 50; //种群规模
const int FoodNumber = NP / 2; //蜜源数量=引领蜂数量(每个蜜源同一时间内只有一只蜜蜂采蜜)跟随蜂数量也是NP/2
const int D = 30; //维数30
const int limit = 200; //经过t次迭代到达limit没找到更好的蜜源,引领蜂转化为侦查蜂
const int maxCycle = 100* D;
const int runtime = 50; //最长运行时间
const int maxFES= 10000 * D; //最大迭代次数
//const double PI = 3.14159265358979323846; //pai值
const double Mt=0.1;
const int Gen = maxFES / 100; // Output Gen. 50 (D = 30) 75 (D = 60) 125 (D = 100)

int best; //当前最好的解
double Foods[FoodNumber][D];
double f[FoodNumber];

double trial[FoodNumber];
double prob[FoodNumber];
double solution[D];
double ObjValSol0;
double ObjValSol;
double FitnessSol;
double GlobalMin;
double GlobalMins[runtime];
double lb;
double ub;

double sigma;
double weights[FoodNumber]; //中间值
double weights2[FoodNumber]; //系数wi
int mu; //系数μ
double m[D]; //参数m(g+1)
double m_old[D]; //保存上一代mg
double cc;
double cs;
double mucov;
double mueff;
double ccov;
double damps;
double muti[D][D]; //矩阵相乘的中间值

int FEs, FEsmin, FEsmean, acount, SucNum;
double mean, Std, SucRate, Accept = 0.0; // 1.0e-8; //

typedef double (*FunctionCallback)(double sol[D]);

//#include "dfs.h"

include "cec2014.h" // D = 10 40 * 2500 = 100000 100 * 1000 D = 30 40 * 7500 = 300000 100 * 3000

FunctionCallback function;//另一种,一起调用三十个

typedef struct { //排序数组返回索引
int index;
double value;
}sort_ar;

bool compare(sort_ar a,sort_ar b){
return a.value<b.value; //比较函数,用于sort函数中,升序排列
}

void initRandom(int index) //初始化 侦查蜂xi的初始化
{
int j;
double r;

r = (double)rand()/(double)(RAND_MAX) ;				//第一个随机数有可能一样(初始化r)
for(j=0;j<D;j++)
{
	r = (double)rand()/(double)(RAND_MAX) ;
    Foods[index][j]=r*(ub-lb)+lb;                         //给每个Food[i][j]赋值        侦查蜂公式
}
f[index]=function( Foods[index] );		FEs++;   //迭代次数加一
trial[index]=0;    //迭代次数回到初始状态0

}

void initial() //给每个蜜源赋值
{
int i;
for(i=0;i<FoodNumber;i++)
{
initRandom(i);
}
GlobalMin = f[0];
}

void CalculateProbabilities() //计算适应值
{
int i;
double sum, tempfun;
double fitness[FoodNumber];

for (i=0;i<FoodNumber;i++)
{
	tempfun = f[i];                                                //fi
	if( tempfun >= 0 )
		fitness[i] = 1.0 / ( 1.0 + tempfun );   //fi>=0的适应值   1/(1+fi)
	else
		fitness[i] = 1.0 - tempfun;               //fi<=0的适应值     1+abs(fi)
}

sum=fitness[0];
for (i=1;i<FoodNumber;i++)
{
    sum=sum+fitness[i];
}

for (i=0;i<FoodNumber;i++)
{
     prob[i]=fitness[i]/sum;           //pi=fitnessi/fitness(1到SN)的和
}

}

int Roulette( int k = 0 ) //轮盘赌
{
int i, j;
double r, m;

r = (double)rand()/(double)(RAND_MAX) ;      //r的初始化,在0,1内随机

if ( k == 0 ){
	r = (double)rand()/(double)(RAND_MAX) ;      //r在0,1内随机
	k = (int)(r*FoodNumber) % FoodNumber;
}

r = (double)rand()/(double)(RAND_MAX) ;  //r在0,1内随机
m = 0;
for(i=0; i<FoodNumber; i++)
{
	j = ( i + k ) % FoodNumber;
	m = m + prob[j];                  //m为适应值的累积
	if( r < m )                             //r<m  在i周围产生新蜜源
	{
		break;
	}
}
if( j >= FoodNumber ){
	r = (double)rand()/(double)(RAND_MAX) ;
	j = (int)(r*FoodNumber) % FoodNumber;
}
return j;       //轮盘赌的结果返回j

}

void MemorizeBestSource() //记住当前最优解
{
int i;
double temp;
temp = f[0];
for(i=1;i<FoodNumber;i++)
{
if (f[i] < temp){
temp=f[i];
best=i;
}
}
if(GlobalMin > temp)
GlobalMin = temp;
}

double pc[D][1] = {0}; //协方差矩阵C的进化路径
double ps[D][1] = {0}; //sigma步长的进化路径
double Bmartix[D][D] = {0};
double Dmartix[D][D] = {0};
double Cmartix[D][D] = {0}; //协方差矩阵
double D_1martix[D][D] = {0}; //D的逆矩阵
double B_tmartix[D][D] = {0}; //B的转置
double c[D][D] = {0}; //C的负二分之一次方
double chiN;
double arz[D][FoodNumber] = {0};

void CMAInit(){
int i,j;
double sum = 0.0;

sigma = 0.5;
mu = floor(FoodNumber / 2);             //精英集大小
for(i = 0;i < mu;i++){
    weights[i] = log(mu + 1) - log(i+1);
    sum += weights[i];
}
for(i = 0;i<mu;i++){
    weights2[i] = weights[i] / sum;     //求wi
}
sum = 0.0;
for(i = 0;i<mu;i++){
    sum += pow(weights2[i],2);
}
mueff = 1 / sum;  //mueff

cc = 4.0 /(D+4.0);
cs = (mueff + 2.0) / (D + mueff + 3.0);
mucov = mueff;
ccov = (1.0 / mucov) * 2.0 / pow((D + 1.4),2) + (1.0 - 1.0/mucov) * ((2.0 * mueff - 1.0) / (pow(D + 2.0 , 2) + 2.0 * mueff ));
damps = 1.0 + 2.0 * max(0.0,sqrt((mueff-1) / (D+1.0)) - 1.0) + cs;


for(i = 0;i < D;i++){      //生成单位矩阵
    Bmartix[i][i] = 1;
    Dmartix[i][i] = 1;
    Cmartix[i][i] = 1;
    m[i] = ((double)rand() / (double)(RAND_MAX));
    ps[i][0] = 0;
    pc[i][0] = 0;
}
chiN = pow(D,0.5) * (1.0 - 1.0 / (4.0 * D) + 1.0 / (21.0 * pow(D,2)));

}

bool mutiply(double *a,double *b,int dim,double * mut){
int i,j,k;

for(i = 0;i < dim;i++){
    for(j = 0;j < dim;j++){
        mut[i*dim+j] = 0.0;
        for(k=0; k < dim;k++){
            mut[i*dim+j] += a[i*dim+k] * b[k*dim + j];
        }
    }
}
return true;

}

//求向量的2范数
double norm(double arc[D][1]){
double ans = 0;
for(int i=0;i<D;i++){
ans += pow(arc[i][0],2);
}
ans = sqrt(ans);
return ans;
}

//求对角矩阵的逆矩阵
bool DiagonalMatrixInverse(double arc[D][D],int n,double invarc[D][D]){
int i;
for(i = 0;i<n;i++){
invarc[i][i] = 1.0/arc[i][i];
}
return true;
}

//矩阵转置
bool transposition(double arc[D][D],int n,double trarc[D][D]){
int i,j;
for(i = 0;i<n;i++){
for(j = 0;j<n;j++){
trarc[i][j] = arc[j][i];
}
}
return true;
}

/雅克比迭代法特征分解,matrix为目标矩阵,dim矩阵行列数,precision精度,max最大迭代次数,matrix为特征值矩阵
/
bool Jacobi(double
matrix, int dim, double
eigenvectors, double precision, int maxtime)
{
for (int i = 0; i < dim; i++) { //单位矩阵
eigenvectors[idim + i] = 1.0f;
for (int j = 0; j < dim; j++) {
if (i != j)
eigenvectors[i
dim + j] = 0.0f;
}
}
int nCount = 0; //current iteration
while (1) {
//找到矩阵上三角部分绝对值最大的元素的行列数
double dbMax = matrix[1];
int nRow = 0;
int nCol = 1;
for (int i = 0; i < dim; i++) { //row
for (int j = 0; j < dim; j++) { //column
double d = fabs(matrix[i*dim + j]);
if ((i != j) && (d > dbMax)) {
dbMax = d;
nRow = i;
nCol = j;
}
}
}

	if (dbMax < precision)     //precision check
		break;
	if (nCount > maxtime)
        break;                 //iterations check


	nCount++;

	double dbApp = matrix[nRow*dim + nRow];
	double dbApq = matrix[nRow*dim + nCol];
	double dbAqq = matrix[nCol*dim + nCol];
	//compute rotate angle
	double dbAngle = 0.5*atan2(-2.0 * dbApq, dbAqq - dbApp);
	double dbSinTheta = sin(dbAngle);
	double dbCosTheta = cos(dbAngle);
	double dbSin2Theta = sin(2.0 * dbAngle);
	double dbCos2Theta = cos(2.0 * dbAngle);
	matrix[nRow*dim + nRow] = dbApp*dbCosTheta*dbCosTheta +
		dbAqq*dbSinTheta*dbSinTheta + 2 * dbApq*dbCosTheta*dbSinTheta;
	matrix[nCol*dim + nCol] = dbApp*dbSinTheta*dbSinTheta +
		dbAqq*dbCosTheta*dbCosTheta - 2 * dbApq*dbCosTheta*dbSinTheta;
	matrix[nRow*dim + nCol] = 0.5*(dbAqq - dbApp)*dbSin2Theta + dbApq*dbCos2Theta;
	matrix[nCol*dim + nRow] = matrix[nRow*dim + nCol];
	for (int i = 0; i < dim; i++) {
		if ((i != nCol) && (i != nRow)) {
			int u = i*dim + nRow;	//p
			int w = i*dim + nCol;	//q
			dbMax = matrix[u];
			matrix[u] = matrix[w] * dbSinTheta + dbMax*dbCosTheta;
			matrix[w] = matrix[w] * dbCosTheta - dbMax*dbSinTheta;
		}
	}
	for (int j = 0; j < dim; j++) {
		if ((j != nCol) && (j != nRow)) {
			int u = nRow*dim + j;	//p
			int w = nCol*dim + j;	//q
			dbMax = matrix[u];
			matrix[u] = matrix[w] * dbSinTheta + dbMax*dbCosTheta;
			matrix[w] = matrix[w] * dbCosTheta - dbMax*dbSinTheta;
		}
	}


	//compute eigenvector
	for (int i = 0; i < dim; i++) {
		int u = i*dim + nRow;		//p
		int w = i*dim + nCol;		//q
		dbMax = eigenvectors[u];
		eigenvectors[u] = eigenvectors[w] * dbSinTheta + dbMax*dbCosTheta;
		eigenvectors[w] = eigenvectors[w] * dbCosTheta - dbMax*dbSinTheta;
	}
}

return true;

}

double diff[D][D]; //m(g+1)-m(g)
sort_ar sar[FoodNumber];

void CMAgenerate(){
int i,j,k;
double diff_x[D][mu]; //xg+1-mg
double diff_y[mu][D]; //上面的转置
double hsig;

for(i = 0;i < FoodNumber;i++){            //排序专用结构体
    sar[i].index = i;
    sar[i].value = f[i];
}

sort(sar,sar+FoodNumber,compare);     //algorithm库里的排序函数
for(i = 0;i < mu;i++){
    for(j = 0;j < D;j++){
        m[j] += weights2[i] * Foods[sar[i].index][j];
    }
}
for(j = 0;j < D;j++){
    diff[j][0] = m[j] - m_old[j];
}
bool re = DiagonalMatrixInverse(Dmartix,D,D_1martix);   //求D的逆和B的转置
re = transposition(Bmartix,D,B_tmartix);
re = mutiply(&Bmartix[0][0],&D_1martix[0][0],D, &muti[0][0]);             //五行算Cg^-1/2 * m(g+1)-mg
re = mutiply(&muti[0][0],&B_tmartix[0][0],D,&c[0][0]);
re = mutiply(&c[0][0],&diff[0][0],D,&muti[0][0]);


for(j = 0;j < D;j++){
    ps[j][0] = (1 - cs) * ps[j][0] + sqrt(cs * (2-cs) * mueff) * muti[j][0] / sigma;
}
hsig = norm(ps)/sqrt(1 - pow(1-cs,(2 * FEs / FoodNumber))/ chiN < 1.4+2/(D+1));
for(j = 0;j < D;j++){
    pc[j][0] = (1 - cc) * pc[j][0] + hsig * sqrt(cc * (2-cc) * mueff) * diff[j][0] / sigma;
}

for(i = 0;i < mu;i++){
    for(j = 0;j < D;j++){
        diff_x[j][i] = (Foods[sar[i].index][j] - m[j]) / sigma;
        diff_y[i][j] = diff_x[j][i];
    }
}

//sigma = sigma * exp((cs/damps)*(norm(ps)/chiN - 1));

for(i = 0;i < D;i++){
    for(j = 0;j < D;j++){
        muti[i][j] = 0.0;
        for(k=0; k < mu;k++){
            muti[i][j] += diff_x[i][k] * diff_y[k][j];
        }
    }
}


//更新协方差矩阵
for(i = 0;i < D;i++){
    for(j = 0;j < D;j++){
        c[i][j] = (1.0-ccov) * c[i][j] + ccov * ((1.0/mucov) * pc[i][0] * pc[j][0] + (1.0 - hsig) * cc * (2.0 - cc) * c[i][j]) + ccov * (1.0 - 1.0/mucov) * weights2[i] * muti[i][j] ;//pc[i][0]*pc[j][0]就是伪代码中的pc*pc'
        Dmartix[i][j] = c[i][j];
    }
}

re = Jacobi(&Dmartix[0][0], D ,&Bmartix[0][0] , 1e-4,1000);

for(i = 0;i < D;i++){
    for(j = 0;j < D;j++){
        if(i != j) Dmartix[i][j] = 0.0;
    }
    Dmartix[i][i] = sqrt(abs(Dmartix[i][i]));
    m_old[i] = m[i];
    m[i] = 0;
}

}

double gaussrand(double E, double V) //高斯分布随机数
{
static double V1,V2,S;
static int phase = 0;
double X;

if(phase == 0){
    do		{
		double U1 = (double)rand()/RAND_MAX;
        double U2 = (double)rand()/RAND_MAX;
        V1 = 2 * U1 - 1;
        V2 = 2 * U2 - 1;
        S = V1 * V1 + V2 * V2;
    }while(S >= 1 || S == 0);
	X = V1 * sqrt(-2 * log(S) / S);
} else {
    X = V2 * sqrt(-2 * log(S) / S);
}
phase = 1 - phase;
X = X * V + E;
return X;

}

void SendEmployedBeesStd() //雇佣蜂阶段
{
int i,j;
int neighbour, dim2change;
double r;

for(i=0;i<FoodNumber;i++)
{
	r = (double)rand()/(double)(RAND_MAX) ;
    dim2change=(int)(r * D) % D;

	r = (double)rand()/(double)(RAND_MAX) ;
    neighbour =(int)(r * FoodNumber) % FoodNumber;
	while(neighbour  == i)
    {
		r = (double)rand()/(double)(RAND_MAX) ;
        neighbour =(int)(r*FoodNumber) % FoodNumber;
    }

    for(j=0;j<D;j++)
	{
		solution[j]=Foods[i][j];
	}
	ObjValSol0=f[i];

    r = 2.0 * (double)rand()/(double)RAND_MAX - 1.0;

    /*for(j=0;j<D;j++){
        solution[j]=Foods[i][j]+(Foods[i][j]-Foods[neighbour][j])*r ;        //vij

        if ( solution[j] < lb)
	    solution[j] = lb;
        if ( solution[j] > ub)
		solution[j] = ub;
    }*/

	solution[dim2change]=Foods[i][dim2change]+(Foods[i][dim2change]-Foods[neighbour][dim2change])*r ;        //vij

	while ( solution[dim2change] < lb)
	    solution[dim2change] = lb;
	while (solution[dim2change] > ub)
		solution[dim2change] = ub;

    ObjValSol=function(solution);	 FEs++;

    if (ObjValSol<ObjValSol0)   //如果vi的值大于xi
    {
		trial[i]=0;
		/*for(j=0;j<D;j++){
            Foods[i][j]=solution[j];     //那么xi=vi
		}*/
        Foods[i][dim2change]=solution[dim2change];     //那么xi=vi
        f[i]=ObjValSol;    	SucNum++;//
		if( ObjValSol <= Accept ){
			FEsmin = FEs;
			break;
		}
    } else {
        trial[i]=trial[i]+1;   //迭代次数加一
    }
}

}

void SendOnlookerBeesStd() //跟随蜂阶段
{
int i,j,t;
int neighbour, dim2change;
double r,sum;
bool re;

for(t=0;t<FoodNumber;t++)
{
    sum = 0;
	i = Roulette( );    //i=轮盘赌的结果j

	r = (double)rand()/(double)(RAND_MAX) ;
    dim2change=(int)(r*D) % D;

	r = (double)rand()/(double)(RAND_MAX) ;
    neighbour=(int)(r * FoodNumber) % FoodNumber;
	while(neighbour == i)
    {
		r = (double)rand()/(double)(RAND_MAX) ;
        neighbour=(int)(r*FoodNumber) % FoodNumber;
    }

    re = mutiply(&Bmartix[0][0],&Dmartix[0][0],D,&muti[0][0]);

    arz[dim2change][i] = gaussrand(0.0,1.0);
    for(j=0;j<D;j++)
	{
		solution[j]=Foods[i][j];
	}
	ObjValSol0=f[i];

    r = 2.0 * (double)rand()/(double)RAND_MAX - 1.0;
	//solution[dim2change]=Foods[i][dim2change]+(Foods[i][dim2change]-Foods[neighbour][dim2change])*r ;

    for(j = 0;j < D;j++){
        sum += muti[dim2change][j] * arz[dim2change][i];
    }

    solution[dim2change] = m_old[dim2change] + sigma * sum;   //x取样

    while ( solution[dim2change]<lb)
	    solution[dim2change] =lb;
	while (solution[dim2change]>ub)
		solution[dim2change] = ub;

/* while ( solution[dim2change] < lb )
solution[dim2change] += ub - lb;
while (solution[dim2change] > ub)
solution[dim2change] += lb - ub;
*/
ObjValSol=function(solution); FEs++;

    if (ObjValSol<ObjValSol0)
    {
		trial[i]=0;
        Foods[i][dim2change]=solution[dim2change];
        f[i]=ObjValSol;  SucNum++;//
		if( ObjValSol <= Accept ){
			FEsmin = FEs;
			break;
		}
    } else {
		trial[i]=trial[i]+1; //迭代次数加一
    }

}

}

void SendScoutBees() //侦查蜂阶段
{
int i;

for(i=0;i<FoodNumber;i++)
{
	if( trial[i] > limit )            //i的迭代次数大于limit的话,引领蜂转化为侦查蜂,随机产生新的xi
	{
		initRandom(i);
	}
}

}

void ABC(int Fun)
{
//FunctionCallback functionArr[2] = { &f1, &f2 }; //, &f2, &f3, &f4, &f5, &f6, &f7, &f8, &f9, &f10, &f11, &f12, &f13, &f14, &f15,&f16, &f17, &f18, &f19, &f20, &f21, &f22, &f23, &f24, &f25, &f26, &f27, &f28, &f29, &f30
int iter, run, count, index;
struct Result {
int FEs;
double fit;
};
Result Red[100];
fstream ffit;
int i;

mean=0;		Std=0;    index = 1;   SucRate = 0;
FEsmean = 0;
count = 0;

cout << "Limit = " << limit << endl;
srand(time(NULL));
function( solution );

for(run=0; run<runtime; run++)
{
	initial();
    CMAInit();
	FEs = 0;  FEsmin = 0;   SucNum = 0;
    for (iter=0;iter<maxCycle;iter++)
	{
		SendEmployedBeesStd( );
		if( FEsmin != 0 )
			break;

		MemorizeBestSource();
		CalculateProbabilities();
		SendOnlookerBeesStd();				// Onlookbees Algorithm
		if( FEsmin != 0 )
			break;

		MemorizeBestSource();
		SendScoutBees();
        CMAgenerate();
		if( run == 3 && FEs / Gen == index ){
			Red[index-1].FEs = index * Gen;
			Red[index-1].fit = GlobalMin;
			index++;
		}
	}
	MemorizeBestSource();
	if( run == 3 && FEs / Gen == index ){
			Red[index-1].FEs = index * Gen;
			Red[index-1].fit = GlobalMin;
			index++;
    }

	cout.width(2);
	 cout << run + 1 << ".run:  " << setiosflags(ios::scientific) << setprecision(2) << "  GlobalMin =  " << GlobalMin  << "   Num: " << best + 1
		<< "	FEsMin: "  << FEsmin << "	FEs: "  << FEs << "	Success rate:  " << double( SucNum ) / double( FEs ) << endl;
		GlobalMins[run]=GlobalMin;
	mean=mean+GlobalMin;
	SucRate = SucRate + double( SucNum ) / double( FEs );
	if( GlobalMin <= Accept  ){				//  *** result ***
		FEsmean = FEsmean + FEsmin;
		acount++;
	}
}

/*
ffit.open( "fitfile.dat", ios::out|ios::binary );
ffit.write( (char *)Red, sizeof( Result ) * maxCycle /Gen );
ffit.close( );
cout << endl;
for( i = 0; i < maxCycle /Gen; i++ ){
cout.width(2);
cout << Red[i].cycle << " " << Red[i].fit << " ";
cout << endl;
}
cout << endl;
*/

/*
// *** Evolutionary process data collection EPDC ***
char Filename[30] = "CABC_Test_Fun_";
char buf[10];

itoa( Fun, buf, 10 );
strcat( Filename, buf );
strcat( Filename, ".txt" );

ffit.open( Filename, ios::out);

ffit << "           ***      CABC Algorithm Test Result      ***" << endl << endl;
ffit << "Function " << Fun << endl;
ffit << setiosflags(ios::scientific) << setprecision(2);
ffit << "FEs	" << "GlobalMin		" << endl;

for( i = 0; i < index - 1; i++ ){
	ffit << Red[i].FEs << "	" << Red[i].fit << endl;
}
ffit << endl;
ffit.close( );
cout << endl;

// *** Evolutionary process data collection END ***
*/

if( count != 0 )
	FEsmean = FEsmean / acount;
mean = mean / runtime;
SucRate = SucRate / runtime;
for(run = 0; run < runtime; run++) {
    Std = Std + pow(GlobalMins[run]-mean, 2);
}
Std = sqrt(Std/runtime);
cout << "Means of " << runtime << " runs:  " << setiosflags(ios::scientific) << setprecision(2) << mean << endl;
cout << "Std of " << runtime << " runs:  " << setiosflags(ios::scientific) << setprecision(2) << Std << endl;
cout << "SucRate = " << SucRate << endl;
cout << "Means of FEs = " << FEsmean << endl;
cout << "The number of accept (0.00) is  "<< acount << endl;

}

int main()
{
FunctionCallback functionArr[30] = { &f1, &f2, &f3, &f4, &f5, &f6, &f7, &f8, &f9, &f10, &f11, &f12, &f13, &f14, &f15,
&f16, &f17, &f18, &f19, &f20, &f21, &f22, &f23, &f24, &f25, &f26, &f27, &f28, &f29, &f30 };
fstream ffit;
ffit.open( "ABC30.txt", ios::out );
ffit << " *** ABC Algorithm Test Result ***" << endl << endl;

ffit << "Parameter setting: " << "NP = " << NP << " D = " << D << " MaxFES = " << maxFES << "   runtime = " << runtime << endl << endl;

int i;

for( i = 0; i < 30; i++){

    function = functionArr[i];
    ABC(i + 1);
    fileflag = 0;
    ffit << "Function :"<< i+1 << " Means of " << runtime << " runs:  " << setiosflags(ios::scientific) << setprecision(2) << mean;
    ffit << "   Std of " << runtime << " runs:  "  << Std  << setiosflags(ios::scientific) << setprecision(4) << "    SucRate = " << SucRate << endl;
    ffit << setiosflags(ios::scientific) << setprecision(2) << "Mean( Std ):    "<< mean << "("<< Std << ")";
    ffit << "   Means of FEs = " << FEsmean << "	The number of accept(0.00) is  "<< acount << endl << endl;

}
ffit.close();
return 0;

}

`

posted @ 2024-07-14 09:18  liulaohei  阅读(41)  评论(0)    收藏  举报