给出 n n n个数字,满足 0 ≤ a i ≤ 2048 0\leq a_i\leq 2048 0≤ai≤2048。规定新的2048游戏规则,对于一个序列,每次可从序列中选取两个大小相同的数字,消除它们,然后添加一个大小为两数字之和的新的数字。如果通过这种方式能够获得2048,那么就称这个序列为完美数列。
现在需要统计这 n n n个数字的所有子序列中的完美序列个数。答案对998244353取模。(注意子序列的定义,和字串有区别)
n ≤ 1 × 1 0 5 n\leq1\times10^{5} n≤1×105
值得我写博客的,要么是自己做出来的好题,要么就是背锅题……
这次是后者。
可能这篇题解的作者脑回路有点憨,下面的“元素”、“数字”都是指同一个东西。因为这道题目中序列的元素就是这些数字。
首先,这个题目想要诱导我们去思考2048的游戏规则……我们必须甩开这点重新思考。
首先,能够用来合成2048的数字必须是2的整数次幂。证明略。
其次,对于一些2的整数次幂(幂次不超过11),只要它们的和大于等于2048,那么就一定能够合成2048。这是个充要条件,也就是说,一些2的整数次幂(幂次不超过11)能够合成2048,那么它们的和一定大于等于2048.
注意,题目的要求,不是恰好合成2048,而是能够合成2048
比赛的时候正面去dp,硬是没搞出来。
赛后经过学长的指点,选择了正确的方法。
首先,我们把所有2的整数幂次的数字提取出来,并且按照幂次分类计数。其他的数字不能用于合成2048,但是却可以用于构造子序列,那么如果其他数字有 k k k个,最后对答案的贡献就是 2 k 2^k 2k倍,最后乘上去就行了。
好的,现在看到2的整数次幂的计数结果。一共只有12种数字,我们只需要知道每种数字的个数 c n t 2 i cnt_{2^i} cnt2i。因为在这道题中,子序列中元素的顺序是与题目无关的。
我们知道,对于这12种数字的所有组合方案(注意,同种数字的不同个体要视为不同的元素,因为它们在原来的序列中的下标是不同的),只要组合起来的和大于2048,就可以计入答案,否则就不能计入答案。
我们正难则反,考虑统计有多少种组合的元素和小于2048,最后再用组合总数 2 n − k 2^{n-k} 2n−k减去就可以了。
定义 d p ( i , j ) dp(i,j) dp(i,j)表示考虑了 2 0 2^0 20- 2 i 2^i 2i这些种类的元素,选取元素总和为 j j j的方案数。
转移方程 d p ( i , j ) = ∑ k = 0 k × 2 i ≤ j C c n t 2 i k d p ( i − 1 , j − k × 2 i ) dp(i,j)=\sum\limits_{k=0}^{k\times2^i\leq j}\bold C_{cnt_{2^i}}^{k}dp(i-1,j-k\times2^i) dp(i,j)=k=0∑k×2i≤jCcnt2ikdp(i−1,j−k×2i)
令2的整数次幂的数字个数为m,那么答案就是:
2 n − m ( 2 m − ∑ i = 0 2047 d p ( 11 , i ) ) 2^{n-m}(2^m-\sum\limits_{i=0}^{2047}dp(11,i)) 2n−m(2m−i=0∑2047dp(11,i))
参考代码(和上面有些细节出入,但是原理相同)
#define George_Plover
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXN 200001
#define MAXM 16000001
#define MOD 998244353
#define LL long long
#define RG register
using namespace std;
int n,m,Case;
int mi[]={1,2,4,8,16,32,64,128,256,512,1024,2048};
int a[MAXN];
int dp[20][3000];
int cnt[3000];
int fac[MAXN];
int inv_fac[MAXN];
int s[3000];
int qpow(int x,int y)
{
x%=MOD;
int ret=1;
while(y)
{
if(y&1)
ret=1ll*ret*x%MOD;
x=1ll*x*x%MOD;
y>>=1;
}
return ret;
}
int C(int x,int y)
{
if(!y)return 1;
return 1ll*fac[x]*inv_fac[y]%MOD*inv_fac[x-y]%MOD;
}
int main()
{
fac[0]=1;
for(int i=1;i<MAXN;i++)
fac[i]=1ll*fac[i-1]*i%MOD;
inv_fac[MAXN-1]=qpow(fac[MAXN-1],MOD-2);
for(int i=MAXN-2;i>=0;i--)
inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%MOD;
while(scanf("%d",&n)&&n)
{
memset(dp,0,sizeof(dp));
for(int i=0;i<=11;i++)
cnt[mi[i]]=0;
int sum=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
bool flag=0;
for(int j=0;j<12;j++)
{
if(a[i]==mi[j])
{
cnt[a[i]]++;
flag=1;
}
}
if(!flag)
sum++;
}
for(int i=0;i<=min(cnt[1],2048);i++)
dp[0][i]=C(cnt[1],i);
for(int i=1;i<12;i++)
{
for(int j=0;j<=2048;j++)
{
for(int k=0;k*mi[i]+j<=2048&&k<=cnt[mi[i]];k++)
{
dp[i][k*mi[i]+j]+=1ll*dp[i-1][j]*C(cnt[mi[i]],k)%MOD;
dp[i][k*mi[i]+j]%=MOD;
}
}
}
int ans=qpow(2,n-sum);
for(int i=0;i<2048;i++)
ans=(ans-dp[11][i])%MOD;
printf("Case #%d: %lld\n",++Case,(1ll*ans*qpow(2,sum)%MOD+MOD)%MOD);
}
return 0;
}