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;
}