题解:P14636 [NOIP2025] 清仓甩卖 / sale

· · 题解

勘误:11.30 15:34 发现一处笔误,请求重新审核

计数好题,主要难点在于推式子(但我赛时没写完……)

显然答案与顺序无关,则我们先对 a 数组从小到大排序。

我们再考虑 $m\ne2$ 的情况。对于 $m=1$,显然 $ans=2^n$。而对于 $m>2$,我们考虑如何将其转换到 $m=2$ 上。我们枚举 $i$ 为被卡掉的那个数的位置,$j$ 为第一个去卡 $i$ 的数的位置,$k$ 为 $i$ 前面最大的数的位置,$t$ 为 $[i+1,n]$ 区间内 $2$ 的个数,则答案为 $$\sum_{i=1}^n\sum_{j=1}^{i-1}\sum_{t=0}^{n-i}\binom{n-i}{t}\binom{i-j-1}{m-n+i-2-t}(1+\sum_{k=1}^{j-1}2^{k-1})[a_j<a_i \land 2a_j>a_i \land a_j+a_k<a_i]$$ 直接做是 $O(n^4)$ 的,不可接受,我们考虑优化。 发现 $k$ 的贡献可以用等比数列求和来计算,即 $1+\sum_{k=1}^{j-1}2^{k-1}[a_k<a_i-a_j]=2^K$,其中 $K$ 表示最大的满足 $a_k<a_i-a_j$ 的 $k$,这可以通过二分求出。时间复杂度就能被优化到 $O(n^3\log n)$。 :::info[等比数列求和] $$\sum_{i=0}^nx^i=\frac{x^{n+1}-1}{x-1}$$ 证明: 设 $$t=\sum_{i=0}^nx^i$$ $$xt=\sum_{i=1}^{n+1}x^i$$ 两式相减得 $$(x-1)t=x^{n+1}-1$$ $$t=\frac{x^{n+1}-1}{x-1}$$ $$Q.E.D$$ ::: 此时答案为 $$\sum_{i=1}^n\sum_{j=1}^{i-1}\sum_{t=0}^{n-i}\binom{n-i}{t}\binom{i-j-1}{m-n+i-2-t}2^K[a_j<a_i \land 2a_j>a_i]$$ 观察到 $\sum_{t=0}^{n-i}\binom{n-i}{t}\binom{i-j-1}{m-n+i-2-t}$ 是一个范德蒙德卷积的形式,答案为 $\binom{n-j-1}{m-n+i-2}$,我们就可以再省下一只 $O(n)$,优化到 $O(n^2\log n)$。 :::info[范德蒙德卷积] 名字听起来很吓人,但原理很简单。 $$\sum_{t=0}^{n}\binom{n}{t}\binom{m}{k-t}=\binom{n+m}{k}$$ 证明:考虑一共有 $n+m$ 个物品,从中选 $k$ 个。这件事情等价于在 $n$ 个物品中先选 $t$ 个,再在剩下 $m$ 个物品中选 $k-t$ 个。改变的仅仅是枚举顺序。 ::: 此时答案为 $$\sum_{i=1}^n\sum_{j=1}^{i-1}\binom{n-j-1}{m-n+i-2}2^K[a_j<a_i \land 2a_j>a_i]$$ 我们发现由于 $2^K$ 这一项的阻碍,此时 $i$ 和 $j$ 的贡献已经不能再往下拆了,所以我们考虑每次 $O(1)$ 求 $K$ 来把时间复杂度降到 $O(n^2)$。因为 $i$ 不变时,从小到大枚举 $j$,则此时 $a_i-a_j$ 单调不升,我们就可以用一个指针维护 $K$ 的位置,做到均摊 $O(1)$。 :::success[Code] ```cpp #include<bits/stdc++.h> #define For(i,j,k) for(int i=j;i<=k;i++) #define dFor(i,j,k) for(int i=j;i>=k;i--) using namespace std; #define MAXN 5005 #define Mod 998244353 int n,m,a[MAXN],b[MAXN],p[MAXN],fra[MAXN],inv[MAXN]; int Pow(int x,int y){ int ans=1; while(y){ if(y&1){ ans=1ll*ans*x%Mod; } x=1ll*x*x%Mod; y>>=1; } return ans; } void init(){ p[0]=1;fra[0]=1; For(i,1,5000){ p[i]=1ll*p[i-1]*2%Mod; fra[i]=1ll*fra[i-1]*i%Mod; } inv[5000]=Pow(fra[5000],Mod-2); dFor(i,4999,0){ inv[i]=1ll*inv[i+1]*(i+1)%Mod; } } int C(int n,int m){ if(n<0||m<0||m>n) return 0; return 1ll*fra[n]*inv[n-m]%Mod*inv[m]%Mod; } int f[MAXN]; int main(){ freopen("sale11.in","r",stdin); freopen("sale11.out","w",stdout); ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); init(); int c,T; cin>>c>>T; while(T--){ cin>>n>>m; For(i,1,n){ cin>>a[i]; } sort(a+1,a+n+1); int ans=0; For(i,1,n){ int k=i-1; For(j,1,i-1){ if(a[j]<a[i]&&a[j]*2>a[i]){ while(a[k]>=a[i]-a[j]){ k--; } ans=(ans+1ll*p[k]*C(n-j-1,m-2-n+i)%Mod)%Mod; } } } cout<<(p[n]-ans+Mod)%Mod<<endl; } return 0; } ``` ::: 后记:做完这道题后,我感觉组合计数题最难的就是写出一个多项式算法,这一步有时候要花很长时间,而优化则是无脑搞就行了,基本一下子就搞定了。