题目
题目描述
有一个大小为 n 的集合 S, 它由 n 个二元组 (i,xi)(1≤i≤n) 组成。 我们用序列 si(1≤i≤n)来表示属于 S 的二元组 (i,si)。 定义一个集合的权值为属于它的二元组的 x 之积, 即二元组的第二个元素之积。 求出 S 的所有非空子集的所有非空子集的权值之和。
输入
输入包含两行。第一行输入一个正整数 n, 第二行输入 n 个正整数,表示 s1∼n。
输出
输出包含一行一个整数,表示答案。 答案对 998244353=119×223+1 取模。
样例
输入
2
2 3
输出
16
解释
S={(1,2),(2,3)}, 为了方便,我们省略二元组的第一个元素,记 S={2,3}。
{2,3} 的非空子集有:{2,3},{2},{3}。
{2,3} 的非空子集的权值之和为:2×3+2+3=11。
{2} 的非空子集的权值之和为:2。
{3} 的非空子集的权值之和为:3。
所以答案为 16。
数据规模与约定
本题采用子任务测试的形式。 仅当你通过了子任务中所有的测试点, 你才能得到该子任务对应的分数,否则不得分。
对于 100% 的数据,有 1≤si<998244353。
子任务 1(12 分):n≤8。
子任务 2(20 分):n≤15。
子任务 3(27 分):n≤5×10^3,∀i,si=1。
子任务 4(41 分):n≤5×10^3。
子任务 5(100 分):n≤10^5。
题解
–听说是一道dp呢,不过首先要知道一个神奇的概念对答案的贡献
因为任何一个大小为 x 的子集的子集 都包含在 2^(n-x) 个子集里
所以说,这个子集的子集对答案的贡献就是 它的元素之积 * 2^(n-x)
–然后是状态定义:f[i][j]表示在前 i 数中选 j 个数出来构成子集的积的和 (必须选第i个)
转移方程:f[i][j] = f[i−1][j] + ai × f[i−1][j−1]
注意:f[i][0]=1
最终的答案是:f[n][1]*2^(n-1)+f[n][2]*2^(n-2)……f[n][n]*2^(n-n)
时间复杂度:O(n^2)
至于子任务5,我只有一脸冷漠
听大佬说:
考虑生成函数。将每个数看作 Ai(x) = 1 + ai*x,则 ∏(n,i=1)
Ai 的 i 次项系数就表示在 n 个数中选 i 个数它
们的积的和。由于模数为 998244353,因此使用分治 NTT,
在 O(n*log^2n) 的时间复杂度内求出f[n][i]。
就交给大佬们咯
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=5005;
const int mod=998244353;
int n;
int a[MAXN];
long long f[MAXN][MAXN];
long long ans;
long long Pow(int b){
long long ans=1,a=2;
while(b){
if(b&1)
ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
f[i-1][0]=1;
for(int j=1;j<=i;j++){
f[i][j]=f[i-1][j]+a[i]*f[i-1][j-1];
f[i][j]%=mod;
}
}
for(int i=1;i<=n;i++){
ans+=f[n][i]*Pow(n-i);
ans%=mod;
}
cout<<ans;
return 0;
}