题解:P12416 多项式高手

· · 题解

Solution

哎我刚开始把这个题想的太简单了,过一会发现又把它想的太难了 (主要是看到其他人的代码很短)

前置知识:O(n \sqrt n)1n 的划分数,即对 n_0 = 1,2,\cdots,n 求出 \sum a_i = n_0a_i \le a_{i+1}a 的组数。

对应这种拆分问题考虑使用 Ferrers 图辅助理解。比如这样:

考虑这张图的最大内接正方形,假设边长为 h

则可以将图形分为 h \times h 的正方形和两个最大值 \le h 的整数拆分。所以我们可以得到生成函数为:

\sum_{h \ge 1} x^{h \times h} \prod_{j=1}^h (\dfrac{1}{1-x^j})^2

显然 h 的值域是 O(\sqrt n) 的。而后面那个乘积的组合意义是:对于每个权值为 1h 的物品,做两遍完全背包(这一步很巧妙,可以直接规避卷积操作。换句话说,在 DP 的过程中已经把卷积做了一遍。)。这样我们可以 O(n) 的完成 h \leftarrow h+1 的变化。

代码长这样:

ffor(h,1,B) {
  ffor(i,h,m) dp[i]=(dp[i]+dp[i-h])%MOD;
  ffor(i,h,m) dp[i]=(dp[i]+dp[i-h])%MOD;
  ffor(j,h*h,m) cnt[j]=(cnt[j]+dp[j-h*h])%MOD;
}
回到这道题。数据保证了:要么 $n$ 极其小可以爆搜($n=2$),要么 $n \le m \le 2n-1$。 将题目改为:有 $n$ 个物品,第 $i$ 个物品的权值为 $\sum_{j=n-i+1}^n b_j$,大小为 $i$。选出若干个物品,使得物品大小和为 $m$。每组方案的权值为所有物品权值之和。求出方案权值之和。 有一个比较直接的思路:对于每个 $i$ 和 $t$,求出有多少种方案能包含至少 $t$ 个 $i$ 物品。而他等于 $m-it$ 的划分数(可以构建双射。但是这个结论真的对吗?) 写出代码(其中 `suf` 数组表示后缀和) ```cpp ffor(j,1,n) ffor(t,1,m/j) ans=(ans+1ll*cnt[m-j*t]*suf[n-j+1])%MOD; ``` 注意,$m-it$ 划分的时候,有可能会选出大小大于 $n$ 的数!所以这个双射不满足。 不过很容易修正——那些不符合要求的情况,可以直接对应为 $m-(n+1)$,$m-(n+2)$,$\cdots$,$1$(考虑删掉最大的物品。而删去最大的物品之后,$m' \le n$ 所以不会出现不合法的情况了!) 再对 `cnt` 数组套一个前缀和即可。 代码: ```cpp #include<bits/stdc++.h> #define ffor(i,a,b) for(int i=(a);i<=(b);i++) #define roff(i,a,b) for(int i=(a);i>=(b);i--) using namespace std; const int MAXN=2e5+10,B=330,MOD=1e9+7; int n,m,b[MAXN],suf[MAXN]; int cnt[MAXN],dp[MAXN],pre[MAXN]; signed main() { ios::sync_with_stdio(false),cin.tie(0),cout.tie(0); cin>>n>>m; ffor(i,1,n) cin>>b[i]; roff(i,n,1) suf[i]=(suf[i+1]+b[i])%MOD; if(n==2) { int ans=0; ffor(j,0,m) if(j<=m-j) ans=(ans+1ll*b[1]*j%MOD+1ll*b[2]*(m-j))%MOD; return cout<<(ans%MOD+MOD)%MOD,0; } pre[0]=cnt[0]=dp[0]=1; ffor(h,1,B) { ffor(i,h,m) dp[i]=(dp[i]+dp[i-h])%MOD; ffor(i,h,m) dp[i]=(dp[i]+dp[i-h])%MOD; ffor(j,h*h,m) cnt[j]=(cnt[j]+dp[j-h*h])%MOD; } int ans=0; pre[0]=1; ffor(i,1,m) pre[i]=(pre[i-1]+cnt[i])%MOD; ffor(j,1,n) ffor(t,1,m/j) ans=(ans+1ll*cnt[m-j*t]*suf[n-j+1])%MOD; m-=n+1; ffor(j,1,n) ffor(t,1,m/j) ans=(ans-1ll*pre[m-j*t]*suf[n-j+1])%MOD; cout<<(ans%MOD+MOD)%MOD; return 0; } ```