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

· · 题解

感觉不是很难啊。可能是我比较擅长这种东西吧。

随机写了一下通过了大样例。目前能通过洛谷数据。

solution

首先考虑这个东西和贪心的关系。

对于任意给定的定价 w_i \in \{1, 2\},小 R 能买到的最大价值实际上等价于直接选取原价最大的 m 个糖果。因此,题目等价于:有多少种方案,使得贪心策略选取的糖果总价值等于该定价方案下的 0/1 背包最优解。

按性价比 \dfrac{a_i}{w_i} 降序购买。

发现贪心策略在只有 12 两种重量时,仅在一种情况下会得到非最优解:

直接计算合法方案很难,我们考虑容斥。

我们需要统计满足上述导致失败的方案数。为了不重不漏,我们枚举第一个导致失败的点 u 和用来填补的点 v

将所有糖果按原价 a_i 从大到小排序。 枚举 u 作为第一个被跳过的 2 元糖果。根据 u,我们将其他糖果 i 分为三类:

对于固定的 uw_u=2),要使其成为因没钱被跳过的糖果,必须满足在 u 之前被考虑并购买的糖果总花费恰好为 m-1,且包含 A 中的所有糖果(花费 w_i),以及 Bw_i=1 的糖果(花费 1)。

N_A = |A|N_B = |B|,设 k_AA 中取 w=2 的个数,k_BB 中取 w=1 的个数。

总花费 = (N_A - k_A) \times 1 + k_A \times 2 + k_B \times 1 = N_A + k_A + k_B

方案数转为从 $N_A+N_B$ 个位置中选 $m-1-N_A$ 个位置贡献额外权重的方案数: $$ \binom{N_A + N_B}{m - 1 - N_A} $$ 在 $u$ 被跳过之后,剩下的钱只有 $1$。 $B$ 中剩余的糖果必然是 $w=2$(否则会在 $u$ 前买),买不起,跳过。 $C$ 中的糖果按顺序考虑: - 如果是 $w=2$,买不起,跳过。 - 遇到的第一个 $w=1$ 的糖果即为 $v$。 - 如果遍历完 $C$ 也没遇到 $w=1$,则 $v$ 不存在。 对于固定的 $u$ 和 $v$,方案是坏的当且仅当:存在已经购买的一元糖果 $z$ 使得 $ a_z < a_u - a_v $。 其中 $z$ 可以来自 $A$($w_z=1$)或 $B$($w_z=1$)。 为了计算方便,我们计算不满足交换条件的方案,然后用总数减去。 对于所有满足 $a_z < a_u - a_v$ 的候选 $z$,其 $w_z$ 都必须被迫取 $2$(如果是 $A$ 中)或者不能取 $1$(如果是 $B$ 中,即被迫取 $2$)。 记阈值 $D = a_u - a_v$,设 $S$ 为 $A \cup B$ 中满足 $a_i < D$ 的元素集合。 要避免交换,必须强制 $S$ 中的所有元素都取 $w=2$,这会固定 $S$ 中元素对 $k_A, k_B$ 的贡献: - $A \cap S$ 中的元素必须是 $2$ $\Rightarrow$ $k_A$ 增加 $|A \cap S|$。 - $B \cap S$ 中的元素必须是 2 $\Rightarrow$ $k_B$ 不增加(因为 $k_B$ 统计的是 $w=1$)。 剩余可以任选的:$(N_A + N_B) - |S|$。 剩余需要的计数:$(m - 1 - N_A) - |A \cap S|$。 方案数: $$ \binom{(N_A+N_B) - |S|}{(m-1-N_A) - | A \cap S|} $$ 于是当前 $(u, v)$ 对的贡献: $$(\binom{N_A + N_B}{m - 1 - N_A} - \binom{(N_A+N_B) - |S|}{(m-1-N_A) - | A \cap S|} ) \times 2^c$$ 其中 $c$ 表示 $v$ 之后的 $C$ 中元素可以任意取的值。 复杂度 $O(T\times n^2)$。 :::success[code] ```cpp #include<bits/stdc++.h> using namespace std; #define int long long inline int read(){ int s=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();} while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar(); return s*w; } inline void out(int x){ if(x==0){putchar('0');return;} int len=0,k1=x,c[10005]; if(k1<0)k1=-k1,putchar('-'); while(k1)c[len++]=k1%10+'0',k1/=10; while(len--)putchar(c[len]); }const int N=5e3+5,mod=998244353; int fact[N],invf[N],pow2[N],a[N]; int addmod(int x,int y){x+=y;if(x>=mod)x-=mod;return x;} int submod(int x,int y){x-=y;if(x<0)x+=mod;return x;} int mulmod(int x,int y){return x*y%mod;} int qpow(int a,int b){ int ans=1; while(b){ if(b&1)ans=mulmod(ans,a); b>>=1;a=mulmod(a,a); }return ans; }int inv(int n){return qpow(n,mod-2);} void init(){ fact[0]=1,invf[0]=1; for (int i=1;i<N;i++) { fact[i]=mulmod(fact[i-1],i); invf[i]=inv(fact[i]); }pow2[0]=1; for(int i=1;i<N;i++)pow2[i]=mulmod(pow2[i-1],2); }int c(int n,int r){ if(r<0||r>n)return 0; return mulmod(fact[n],mulmod(invf[r],invf[n-r])); }void solve(){ int n=read(),m=read(),tot=0; for(int i=1;i<=n;i++)a[i]=read(); sort(a+1,a+n+1,greater<int>()); for(int u=1;u<=n;u++){ int idx=u+1; while(idx<=n&&2*a[idx]>a[u])idx++; int A=u-1,B=idx-1-u,C=n-idx+1; int tmp=c(A+B,m-1-A); if(tmp==0)continue; vector<pair<int,int>>cnt; int ptrA=1,ptrB=u+1; while(ptrA<=u-1&&ptrB<idx) { if(a[ptrA]>=a[ptrB]){cnt.push_back({a[ptrA],0});ptrA++;} else{cnt.push_back({a[ptrB],1});ptrB++;} }while(ptrA<=u-1){cnt.push_back({a[ptrA],0});ptrA++;} while (ptrB<idx){cnt.push_back({a[ptrB],1});ptrB++;} int ptrS=cnt.size()-1,K_A=0,K_B=0; for(int i=0;i<=C;i++) { int val=0; if (i<C)val=a[idx+i]; int D=a[u]-val; while(ptrS>=0&&cnt[ptrS].first<D) { if(cnt[ptrS].second==0)K_A++; else K_B++;ptrS--; }int g=c(A+B-K_A-K_B,m-1-A-K_A),b=submod(tmp,g); if(b>0){ int rem_C=(i<C)?(C-1-i):0,term=mulmod(b,pow2[rem_C]); tot=addmod(tot,term); } } }int ans=submod(pow2[n],tot); cout<<ans<<"\n"; } signed main() { freopen("sale11.in","r",stdin); freopen("sale.out","w",stdout); init();int c=read(),t=read(); while(t--)solve(); return 0; } ``` :::