用遗传算法解决子集和问题

浅谈遗传算法:https://www.cnblogs.com/AKMer/p/9479890.html

子集和问题:

Description

给你\(n\)个数,问是否存在一个子集使得子集中数字之和等于\(m\),为了增加难度,请输出小于等于\(m\)的子集和中最大的那一个。

Input

输入一共两行
第一行两个数\(n,m(n\leqslant1000,m\leqslant100000);\)
第二行\(n\)个数,每个数字不大于\(100\)

Output

一个数字,表示能用给出的数字拼出的小于等于\(m\)的最大的值

Sample Input

5 10
2 2 6 5 4

Sample Output

10
(2+2+6)或(6+4)

遗传算法经典题目,如果不会遗传算法的可以去看看顶上那篇博客。
数据其实可以再大一点的,只不过\(01\)背包只能造这个范围的数据了。
我自己造的数据:https://files.cnblogs.com/files/AKMer/%E5%AD%90%E9%9B%86%E5%92%8C%E9%97%AE%E9%A2%98.zip
遗传代数多一点就可以A掉了。不得不说,遗传算法的正确性还是挺鲁棒的。

时间复杂度:\(O(欧洲人)\)
空间复杂度:\(O(n)\)

代码如下:

#include <ctime>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=1005;
const int chr_cnt=20;
const int cross_cnt=10;

bool use[maxn],work=1;//use[i]记录第i个选还是不选,work表示遗传算法是否继续工作
int num[maxn],f[chr_cnt+5],g[chr_cnt+5],f1[chr_cnt+5];//num记录第i个数字,f记录第i条染色体的估价函数,f1用来缓存f,g存确定选择法下第i条染色体会被选择几次
int n,m,sum,dis,ans,var_cnt,tim;//n表示数字个数,m表示所求之和,sum记录所有数字之和,dis记录当前种群最优解离目标m差多少。
//ans记录最后答案,var_cnt记录变异次数,tim存连续最优解不变的代数

int read() {
	int x=0,f=1;char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
	return x*f;
}//快读

int random(int limit) {
	return rand()%limit;
}//rand出一个[0,limit-1]之内的数

struct Chromosome {
	int gene[maxn];//用一个长度为n的01串记录该染色体
}chr[chr_cnt+5],new_chr[chr_cnt+5];

void Initialization() {
	if(sum<=m) {
		for(int i=1;i<=n;i++)
			use[i]=1;
		return;//如果总和加起来都不超过m就直接全选带走
	}
	int pps=sum/m+1;
	for(int i=1;i<=chr_cnt;i++) {
		for(int j=1;j<=n;j++)
			if(!random(pps))chr[i].gene[j]=1;
			else chr[i].gene[j]=0;
	}dis=m;//随机20条染色体
}

void select() {
	for(int i=1;i<=chr_cnt;i++) {
		f[i]=0;
		for(int j=1;j<=n;j++)
			f[i]+=num[j]*chr[i].gene[j];//先令f[i]=i号染色体表示的总和
		if(f[i]<=m&&(m-f[i]<dis)) {//如果更新了最优解就更新
			dis=m-f[i];tim=0;
			for(int k=1;k<=n;k++)
				use[k]=chr[i].gene[k];//更新use
		}
		if(f[i]<=m)f[i]=sum+(m-(m-f[i]));//这样设计可以使得低于m并且靠近m的数适应度较其他都大
		else f[i]=sum-(f[i]-m);//因为大于m明显不优,所以往小了设
		if(!dis) {work=0;return;}//如果出最优解了就return
	}
	tim++;int res=0;
	for(int i=1;i<=n;i++)
		res+=f[i];
	int cnt=0;//cnt存新一代种群的染色体条数
	for(int i=1;i<=chr_cnt;i++) {
		g[i]=1.0*f[i]/res*chr_cnt;
		cnt+=g[i];
	}//求g数组
	for(int i=1;i<=chr_cnt;i++)
		f1[i]=f[i];
	while(cnt<chr_cnt)g[random(chr_cnt)+1]++,cnt++;
	cnt=0;
	for(int i=1;i<=chr_cnt;i++) {
		for(int j=1;j<=g[i];j++) {
			new_chr[++cnt]=chr[i],f[cnt]=f1[i];
			if(cnt==chr_cnt)break;
		}
		if(cnt==chr_cnt)break;
	}
	for(int i=1;i<=chr_cnt;i++)
		chr[i]=new_chr[i];//以上是确定性选择法
}

int calc(Chromosome u) {//计算染色体u的适应度
	int res=0;
	for(int i=1;i<=n;i++)
		res+=u.gene[i]*num[i];
	if(res<=m)res=sum+(m-(m-res));
	else res=sum-(res-m);
	return res;//意思如select中所述
}

bool check(int u,int v,int l,int r) {//判断交换u号染色体和v号的区间[l,r]之后是否会使两者的适应度都降低
	Chromosome a=chr[u],b=chr[v];
	for(int i=l;i<=r;i++)
		swap(a.gene[i],b.gene[i]);//交换
	int tmpa=calc(a),tmpb=calc(b);
	return tmpa<f[u]&&tmpb<f[v];//判断
}

void cross() {//交叉
	for(int i=1;i<=cross_cnt;i++) {
		int u=random(chr_cnt)+1,v=random(chr_cnt)+1;
		int l=random(n)+1,r=random(n)+1;//random出要交换的染色体与区间
		if(l>r)swap(l,r);
		if(check(u,v,l,r))continue;//如果不优就放弃此次机会
		for(int i=l;i<=r;i++)
			swap(chr[u].gene[i],chr[v].gene[i]);
		f[u]=calc(chr[u]),f[v]=calc(chr[v]);//否则交换并且重新计算适应度
	}
}

void variety() {//变异
	for(int i=1;i<=var_cnt;i++) {
		int u=random(chr_cnt)+1,pos=random(n)+1;//random出要变异的染色体与基因位置
		Chromosome tmp=chr[u];tmp.gene[pos]^=1;
		if(calc(tmp)<f[u])continue;//如果变异后不优就放弃此次机会
		chr[u].gene[pos]^=1;
		f[u]=calc(chr[u]);//否则变异并且更新适应度
	}
}

void Genetic() {
	if(sum<=m)return;//如果全选还没有m那么大就直接出结果
	var_cnt=n*20*0.1;//变异概率为0.1
	while(work) {
		select();//选择
		if(tim>2000)return;//超过2000代就放弃治疗
		if(work) {
			cross();//交叉
			variety();//变异
		}
	}
}

int main() {
	srand(time(0));
	n=read();m=read();
	for(int i=1;i<=n;i++)
		num[i]=read(),sum+=num[i];//读入
	Initialization();//初始化
	Genetic();//遗传算法
	for(int i=1;i<=n;i++)
		if(use[i])ans+=num[i];
	printf("%d\n",ans);//出结果
	return 0;
}

巧的是我后来看到一道题和这个题意思一模一样……有时间再来填坑。
Joy OJ1340送礼物:http://www.joyoi.cn/problem/tyvj-1340

posted @ 2018-08-16 14:54  AKMer  阅读(449)  评论(0编辑  收藏  举报