Rikka with Subset
题意:有一个正整数数列 a[ ] ,长度(n<=50)。b[i] 表示元素和为 i 的集合个数。给你一个数列 b[ ] ,长度(m<=10000),让你求 a[ ],并按照其字典序最小输出。
容易想到,0的个数就是log2(b[0]),一的个数就是b[1]/b[0].但是题目明确是正整数,所以1的个数其实就是b[1].广义的讲,第一个不为零的b[i]表示a[ ]数组中i的个数为b[i],以此类推,那么有
和为j+i的组合数−和为j的组合数(元素中没有i)=和为j+i的组合数(元素中没有i)
转化为递推式就是 b[j]=b[j]-b[j-i],i是当前b[ ]数组中第一个不为零的b[i].
#include <iostream>
#include <stdio.h>
using namespace std;
const int ma=1e4+10;
int b[ma],a[55];
int main()
{
int t;
scanf("%d",&t);
int n,m;
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<=m;++i)
scanf("%d",&b[i]);
int cnt=0,i=1;
while(cnt<n)
{
while(!b[i]&&i<=m) ++i;
if(i>m) break;
a[cnt++]=i;
for(int j=i;j<=m;++j)
b[j]-=b[j-i];
}
for(int i=0;i<cnt;++i)
printf("%d%c",a[i],i==cnt-1?'\n':' ');
}
return 0;
}
还有另一种写法,是依据公式做的。
如果1的个数为b[1],那么,2的个数就是b[2]-C(b[1],2);
同样3的个数就是b[3]减去{1,1,1},{1,2}的组合数,就是b[3]-C(b[1],3)-C(b[1],1)*C(b[2],1),以此类推。
看一个的AC代码:点击打开链接
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 1e4 + 100;
int b[maxn];
int dp[maxn], num[maxn];
int C(int n, int m)
{
int sum = 1;
for (int i = n - m + 1; i <= n; i++) sum *= i;
for (int i = 1; i <= m; i++) sum /= i;
return sum;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n, m;
scanf("%d%d", &n, &m);
memset(dp,0,sizeof(dp));
memset(num,0,sizeof(num));
for (int i = 0; i <= m; i++)
scanf("%d", &b[i]);
num[0] = log2(b[0]);
num[1] = b[1] / b[0];
dp[0] = b[0];
for (int i = 0; i <= m; i++)
{
for (int j = m; j >= 0; j--)
{
if (dp[j] == 0) continue;
if (num[i] == 0) break;
for (int k = 1; k <= num[i]; k++)
{
if (j + k*i <= m)
{
dp[j + k*i] += dp[j] * C(num[i], k);
}
}
}
if (i + 1 <= m)
{
num[i+1] = (b[i+1] - dp[i+1]) / b[0];
}
}
bool flag = 0;
for (int i = 0; i <= m; i++)
{
for (int j = 1; j <= num[i]; j++)
{
if (!flag) printf("%d", i), flag = 1;
else printf(" %d", i);
}
}
puts("");
}
return 0;
}