原题链接:G-小红的子序列权值和
题目大意:给长度为n的数组,数组由1,2,3,三种数构成,求所有子序列的贡献和是多少,子序列的贡献定义为子序列乘积的因子数目。
思路:可以观察到每一个子序列的乘积都是由1^x*2^y*3^z构成,那么对于这样的数他的因子就是(y+1)*(z+1),判断一个整数因子数目的时候1的个数并不重要。那么对于题目里面的子序列乘积也就是选几个2和选几个3的问题了,虽然1对因子数目没有影响,但是1会对子序列产生影响,例如说对于整数12,数组有2个1的情况下,12可以是2*2*3,也可以是2*2*3*1,也可以是2*2*3*1*1,也可以是2*2*3*1(二个1位置不一样所以子序列也不一样),所以对于每个整数产生的共享还需要乘以2的x次方,x为1的个数。因为数组里面可能有多个2和3,那么就需要使用组合数来计算一个整数的贡献,例如12,如果数组里面有3个2,那么就需要乘上C(3,2)了。
因为题目的数据范围是1e5那么就不可以同时枚举选2和选3的数量了,对于数组的全部贡献可以表示为C(cnt2,1)*(1+1)*C(cnt3,1)*(1+1)*ksm(2,cnt1)+C(cnt2,2)*(2+1)*C(cnt3,1)*(1+1)*ksm(2,cnt1)+......,那么就可以先算2的贡献,再算3的贡献最后相乘。
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N=1e6+10,mod=1e9+7;
ll t[10];//记录1,2,3的数量
ll p[N];//1到n的阶乘
ll ksm(ll a,ll b)
{
ll sum=1;
do
{
if(b&1)sum*=a;
a*=a;a%=mod;sum%=mod;b>>=1;
}while(b);
return sum;
}
ll c(ll a,ll b)//逆元求组合数
{
return p[a]%mod*ksm(p[b],mod-2)%mod*ksm(p[a-b],mod-2)%mod;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
ll n;cin>>n;
p[0]=1;
ll ans1=0,ans2=0,temp=1;
for(int i=1;i<N;i++)
{
p[i]=p[i-1]*i;p[i]%=mod;
}
for(int i=0;i<n;i++)
{
ll x;cin>>x;
t[x]++;
}
for(int i=0;i<=t[2];i++)//先算2
{
ans1=ans1%mod+c(t[2],i)%mod*(i+1)%mod;ans1%=mod;
}
for(int i=0;i<=t[3];i++)//算3
{
ans2=ans2%mod+c(t[3],i)%mod*(i+1)%mod;ans1%=mod;
}
cout<<(ans1%mod*ans2%mod*ksm(2,t[1])%mod-1+mod)%mod;
return 0;
}