P8863 题解

· · 题解

P8863 「KDOI-03」构造数组 题解

做完看了一下题解发现一眼看过去都好复杂

讲一下自己推出来的做法。

首先可以明确,\sum b_i \equiv 1 \pmod 2 的时候,一定无解,因为每次操作都会使整个数列的和增加 2,而 a_i 初始均为 0,所以数列的和始终为偶数,不可能出现 \sum b_i \equiv 1 \pmod 2 的情况。

那么我们我们不妨来转换一下问题,设 m=\dfrac{\sum b_i}{2},那么题意就变为了:给定 m 个括号,每一个括号都形如 (\quad ,\quad),即需要往里面填 2 个数,填的数在 [1,n] 之间,同一括号内的数不相同,并且 i 在 m 个括号中出现 b_i 次。

来手玩一下样例 1。首先只考虑填 1 的话我们的方案就有 C_{4}^{2} 种。

那么接下来我们要填 2。如果我们只考虑填 1 和 2 的话我们的方案数就有 C_{4}^2 \cdot C_{4}^2 种。但是我们注意到我们填 2 的位置是会改变填 3 的方案数的。因为如果填进了已经填过 1 的位置,这个括号就填满了,3 的可选位置就变少了。

此时注意到数据范围 1 \le n \le 5000,\sum b_i \le 30000,可以猜想这道题目可能是 DP。结合上面的思考,我们就应该设计 DP 状态为 f_{i,j},第一维 i 表示填完了前 i 个数,第二维 j 表示有 j 个括号已经被填满。

下面设 s_i=\sum \limits _{k=1}^i b_k。

状态转移时,我们不妨考虑填完前 i-1 个数已经被填满的括号数 k \in [0,j],那么为了让填完的括号数变为 j,我们需要从 s_{i-1}-2k 个填了 1 个数的括号中选择 j-k 个括号填上 i,同时从 m-k-(s_{i-1}-2k)=m+k-s_{i-1} 个空括号中选择 b_i-(j-k) 个填上剩下的 i。所以状态转移方程为:

f_{i,j}=\sum_{k \in [0,j]} f_{i-1,k} \cdot C_{s_{i-1}-2k}^{j-k} \cdot C_{m+k-s_{i-1}}^{b_i-(j-k)}

另外我们注意到,最好控制一下使得这些组合中的数不为负。这样我们可以进一步缩小 k 的取值范围,只有这样时间复杂度才是对的。

综上所述,设: $$ l=\max \{ 0,j-b_i,s_{i-1}-m \} \\ r=\min \{ j,\frac{s_{i-1}}{2} \} $$ 状态转移方程变为: $$ f_{i,j}=\sum_{k \in [l,r]} f_{i-1,k} \cdot C_{s_{i-1}-2k}^{j-k} \cdot C_{m+k-s_{i-1}}^{b_i-(j-k)} $$ 其中初始状态为 $f_{0,0}=1$,最终答案即为 $f_{n,m}$。 注意到 $n \cdot m$ 非常大,要开这么大的 $f$ 数组是不可能的。又注意到 $f_i$ 的转移只和 $f_{i-1}$ 有关,因此用滚动数组优化。 注意到 $j-(j-b_i)=b_i$,所以枚举 $k$ 的循环次数不会太多。时间复杂度大概是 $O(m^2)$,4s 时限下稳过。 代码: ```cpp #include<bits/stdc++.h> #define int long long #define here(x) {cout<<"here "<<x<<endl;} #define putlist(x,st,ed) {for(int ind=st;ind<=ed;ind++)cout<<x[ind]<<' ';cout<<endl;} #define putstl(x) {for(auto TeMp:x)cout<<TeMp<<' ';cout<<endl;} #define putsize(x) cout<<sizeof(x)*1.0/1024/1024<<" MB"<<endl; #define cases int __;cin>>__;while(__--)work(); #define MAXN_INT 2147483647 #define MAXN_LL 9223372036854775807ll #define N 5010 #define M 30010 #define eps 1e-6 #define mp make_pair #define pb push_back using namespace std; const int MOD=998244353; namespace Combination { int qpow(int x,int y) { int res=1; while(y) { if(y&1)res=res*x%MOD; x=x*x%MOD; y>>=1; } return res; } int fac[M],inv[M]; void setup(int lim) { fac[0]=1; for(int i=1;i<=lim;i++) { fac[i]=fac[i-1]*i%MOD; } inv[lim]=qpow(fac[lim],MOD-2); for(int i=lim-1;i>=1;i--) { inv[i]=inv[i+1]*(i+1)%MOD; } } int comb(int n,int m) { if(n<m)return 0; else if(n==m||m==0)return 1; else if(n==0)return 0; else return fac[n]*inv[m]%MOD*inv[n-m]%MOD; } } using namespace Combination; int n,m; int b[N],s[N]; int ol=0,ne=1; int f[2][M]; signed main() { cin>>n; for(int i=1;i<=n;i++) { cin>>b[i]; s[i]=s[i-1]+b[i]; } m=s[n]/2; f[ol][0]=1; setup(M-5); for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++) { int l=max({0ll,j-b[i],s[i-1]-m}); int r=min(j,s[i-1]/2); f[ne][j]=0; for(int k=l;k<=r;k++) { f[ne][j]=(f[ne][j]+f[ol][k]*comb(s[i-1]-2*k,j-k)%MOD*comb(m+k-s[i-1],b[i]-j+k)%MOD)%MOD; } } swap(ol,ne); } cout<<f[ol][m]<<endl; return 0; } /* */ ```