给你n个数和一个给点的m,问你这n个数中能否有某些数的和等于m,如果有,输出字典序最小的那个解。
本题算是一个背包吧,假如不要求输出解,完全可以一维的状态,只要注意下后推和前推,一开始确实被这个坑了,用一维的话,输出的方案会有重复使用某个数的情况,后来改成二维,二维就不存在前推后推的区别了,学背包问题,如果不懂一维二维前推后推的区别,那只能说连皮毛也没学到。
dp[i][j]表示前i个数中能否组成j,能则赋值1,不能则0。
注意这里是前i个数,不是刚好,比如说前5个数,但是并不是这5个数都用了,可能用了第1,3,4个这3个数,这也就是动态规划的精髓了。因为我把已经求解的存在dp数组里了
假如求解dp[i][j]时,假如去掉第i个数的状态(dp[i-1][j-a[i]])已经可达了,那么dp[i][j]同样可达,假如dp[i-1][j-a[i]]不可达,那么很简单,第i个数取了也没用,那么就不取,那么dp[i][j]=dp[i-1][j]。
为了求字典序最小的解,需要一开始按照a[i]降序排序,用pre[i][j]记录这个状态由哪个数转过来的,最后输出即可。
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
int n,m;
int dp[10001][101];
int pre[10001][101];
int a[10001];
int ans[10001];
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
int i,j;
memset(dp,0,sizeof(dp));
memset(pre,-1,sizeof(pre));
int sum=0;
for(i=1;i<=n;i++)
scanf("%d",a+i);
sort(a+1,a+n+1,cmp);
for(i=0;i<=n;i++)
dp[i][0]=1;
for(i=1;i<=n;i++)
for(j=a[i];j<=m;j++)
{
if(dp[i-1][j-a[i]]==1)
{
dp[i][j]=1;
pre[i][j]=i;
}
else
dp[i][j]=dp[i-1][j];
}
if(dp[n][m]==0)
printf("No Solution\n");
else
{
int cnt=0;
while(m!=0)
{
if(pre[n][m]!=-1)
{
int tmp=pre[n][m];
ans[++cnt]=a[tmp];
n=tmp-1;
m-=a[tmp];
}
else
n--;
}
for(i=1;i<cnt;i++)
printf("%d ",ans[i]);
printf("%d\n",ans[cnt]);
}
}
return 0;
}