【HDU 4945】2048ダイナミックプログラミング
題名:
nnを与えるn個の数値、0≤ai≤2048を満たす0 \ leq a_i \ leq 20480≤a私≤2 0 4 8。2048の新しいゲームルールが規定されており、シーケンスについては、シーケンスから同じサイズの2つの数字を毎回選択して削除し、2つの数字の合計である新しい数字を追加することができます。この方法で2048を取得できる場合、このシーケンスは完全シーケンスと呼ばれます。
今、私たちはこのnnを数える必要がありますn個の数のすべてのサブシーケンスにおける完全なシーケンスの数。答えは998244353を法としています。(サブシーケンスの定義は文字列とは異なることに注意してください)
n≤1×1 0 5 n \ leq1 \ times10 ^ {5} ん≤1×1 05
ブログを書く価値はあります。私が自分で作成した良い質問か、連続した質問です...
今回は後者です。
おそらく、このソリューションの作者の脳回路は少し素朴で、次の「要素」と「数値」はすべて同じものを指します。この質問のシーケンスの要素はこれらの数字だからです。
参考アイデア:
まず、このトピックは、2048年のゲームのルールについて考えるように私たちを誘導したいのです...この点を取り除き、再考する必要があります。
-
まず、2048の合成に使用できる数は2の累乗でなければなりません。証明は省略されます。
-
次に、2の整数の累乗(累乗が11を超えない)の場合、それらの合計が2048以上である限り、2048を合成する必要があります。これは必要かつ十分な条件です。つまり、2の整数の累乗(11を超えない累乗)を2048に組み合わせることができ、その合計は2048以上でなければなりません。
タイトルの要件は、2048を合成することだけでなく、2048を合成できることです。
ゲーム中にdpを正面から見ましたが、うまくいきませんでした。
試合後、先輩の指導の後、正しい方法が選ばれました。
まず、整数の2の累乗のすべての数を抽出し、その累乗に従って分類およびカウントします。他の数値は2048の合成には使用できませんが、サブシーケンスの構築には使用できます。したがって、他の数値にkkがある場合k、回答への最終的な貢献は2 k 2 ^ k2k回、最後にそれを乗算します。
では、2の累乗で数えた結果を見てみましょう。数値のタイプは12種類しかないため、各タイプの数を知る必要があるだけですcnt 2 i cnt_ {2 ^ i}c n t2私。この質問では、サブシーケンスの要素の順序は質問とは関係がないためです。
これらの12種類の数値のすべての組み合わせについて、元のシーケンスの添え字が異なるため、同じ種類の数値の異なる個体は異なる要素と見なされることに注意してください。組み合わせた合計が2048より大きい場合に限ります。 、回答に含めることができます。それ以外の場合、回答に含めることはできません。
正反対で、2048未満の要素の組み合わせの数を検討し、最後に組み合わせの総数を使用します2 n − k 2 ^ {nk}2n − kを引くだけです。
dp(i、j)を定義dp(i、j)d p (i 、j )は、2 0 2 ^ 0が考慮されることを意味します20 -2 I 2 ^ I2私の要素のこれらの種類、選択された要素の合計があるJJjの計画の数。
转移方程dp(i、j)= ∑ k = 0 k×2 i≤j C cnt 2 ikdp(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、jk \ times2 ^ i)d p (i 、j )=k = 0Σk × 2I ≤jをCc n t2私Kd p (i−1 、j−k×2私)
2のべき乗の桁数をmとすると、答えは
2 n − m(2 m − ∑ i = 0 2047 dp(11、i))2 ^ {nm}(2 ^ m- \ sum \ limits_ {i = 0} ^ {2047} dp(11、i))2n − m(2メートル−i = 0Σ2 0 4 7d p (1 1 、私))
参照コード(一部詳細は上記と異なりますが、原理は同じです)
#define George_Plover
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <iostream>
#include <iomanip>
#include <algorithm>
#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;
}