专栏文章
题解:P14636 [NOIP2025] 清仓甩卖 / sale
P14636题解参与者 23已保存评论 23
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 22 条
- 当前快照
- 1 份
- 快照标识符
- @mimyore6
- 此快照首次捕获于
- 2025/12/01 17:43 3 个月前
- 此快照最后确认于
- 2025/12/01 17:43 3 个月前
感觉不是很难啊。可能是我比较擅长这种东西吧。
随机写了一下通过了大样例。目前能通过洛谷数据。
solution
首先考虑这个东西和贪心的关系。
对于任意给定的定价 ,小 R 能买到的最大价值实际上等价于直接选取原价最大的 个糖果。因此,题目等价于:有多少种方案,使得贪心策略选取的糖果总价值等于该定价方案下的 0/1 背包最优解。
按性价比 降序购买。
- 若 ,性价比为 。
- 若 ,性价比为 。
发现贪心策略在只有 和 两种重量时,仅在一种情况下会得到非最优解:
-
贪心过程中,因为剩余钱数为 ,跳过了一个性价比很高但买不起的 元糖果 。
-
随后,用这 元钱买了一个 元糖果 (或者没买任何东西,即 不存在,)。
-
如果此时,在已经购买的 元糖果集合中,存在一个糖果 ,满足 (即把 和 换成 会更优),那么贪心策略就失败了。
直接计算合法方案很难,我们考虑容斥。
我们需要统计满足上述导致失败的方案数。为了不重不漏,我们枚举第一个导致失败的点 和用来填补的点 。
将所有糖果按原价 从大到小排序。
枚举 作为第一个被跳过的 元糖果。根据 ,我们将其他糖果 分为三类:
-
集合 ()::
- 无论 是 1 还是 2,其性价比都 。这些糖果一定在 之前被考虑。
-
集合 ( 且 ):。
- 若 ,性价比 ,在 之前考虑(被购买)。
- 若 ,性价比 ,在 之后考虑。
-
集合 ( 且 ):
- 无论 是 1 还是 2,性价比都 ,一定在 之后考虑。
对于固定的 (),要使其成为因没钱被跳过的糖果,必须满足在 之前被考虑并购买的糖果总花费恰好为 ,且包含 中的所有糖果(花费 ),以及 中 的糖果(花费 )。
设 ,,设 为 中取 的个数, 为 中取 的个数。
总花费 = 。
。
方案数转为从 个位置中选 个位置贡献额外权重的方案数:
在 被跳过之后,剩下的钱只有 。
中剩余的糖果必然是 (否则会在 前买),买不起,跳过。
中的糖果按顺序考虑:
- 如果是 ,买不起,跳过。
- 遇到的第一个 的糖果即为 。
- 如果遍历完 也没遇到 ,则 不存在。
对于固定的 和 ,方案是坏的当且仅当:存在已经购买的一元糖果 使得 。
其中 可以来自 ()或 ()。
为了计算方便,我们计算不满足交换条件的方案,然后用总数减去。
对于所有满足 的候选 ,其 都必须被迫取 (如果是 中)或者不能取 (如果是 中,即被迫取 )。
记阈值 ,设 为 中满足 的元素集合。
要避免交换,必须强制 中的所有元素都取 ,这会固定 中元素对 的贡献:
-
中的元素必须是 增加 。
-
中的元素必须是 2 不增加(因为 统计的是 )。
剩余可以任选的:。
剩余需要的计数:。
方案数:
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;
}
```
:::相关推荐
评论
共 23 条评论,欢迎与作者交流。
正在加载评论...