[NOIP2025] 清仓甩卖 个人题解
-
重要观察
观察样例,发现贪心策略大部分情况下都正确,这启示我们计算贪心不正确的定价方案数。
发现最后只会剩下 \(0\) 元或 \(1\) 元,考虑剩下一元时,可能存在已经选的一个 \(w_x=1\) 的糖果 \(x\) ,它不如一个没有选择的 \(w_y=2\) 的糖果 \(y\) ,即 \(a_x>\frac{a_y}{2}\) 且 \(a_x<a_y\)。
剩下 \(0\) 元时类似,相当于剩 \(1\) 元后又选择一个 \(w_z=1\)的糖果 \(z\) 。且 \(a_x+a_z<a_y\) 。
这是仅有的一类不合法情况。
-
观察2
数据范围 \(:\) \(n \le 5000\)
启示可以使用 \(n^2\) 算法。 -
解法
计算不合法方案数。
先从大到小排序 \(a\) 数组。
考虑枚举 \(x\) , \(y\) 。
优先从 \(1\) 到 \(n\) 枚举 \(y\) 。再从 \(y+1\) 到 \(n\) 枚举 \(x\) 。
我们要计算选到 \(y\) 时还剩 \(1\) 元的定价方案数,再乘上糖果 \(z\) 及以后的定价方案数。
对于 \(1 \le i \le y-1\) 的糖果 ,无论令 \(w_i=1\) 或 \(w_i=2\) ,它都会排在 \(y\) 之前,设有 \(f\) 个 \(w_i=2\) 的糖果,则这部分花的总钱数为 \(y-1+f\) 元。
对于 \(y+1 \le i \le x-1\) 的糖果,当 \(w_i=1\) 时,它会排在 \(x\) 之前;当 \(w_i=2\) 时,它会排在 \(x\) 之后,此时可以看成它的价钱为 \(0\) 或 \(1\) 。设有 \(g\) 个 \(w_i=1\) 的糖果,则这部分花的总钱数为 \(g\) 元。
对于 \(i=y\) 的糖果,\(w_i=1\) ,这部分的总钱数是 \(1\)。
以上两部分所花的总钱数应等于 \(m-1\) 。即 \(y+f+g=m-1\) ,可化成 \(f+g=m-y-1\)。考虑到 \(f\) , \(g\) 实际上都是选相应个数使它们的贡献加 \(1\) , 相当于在 \(x-2\) 个数中无序选择 \(m-y-1\) 个数,则 \(x\) 前面的方案数为
\( \begin{pmatrix} x-2 \\ m-y-1 \end{pmatrix} \) .然后考虑寻找合法的糖果 \(z\) :
\(n^3\) 做法
从 \(x+1\) 到 \(n\) 枚举 \(z\) , 找到第一个满足 \(a_x+a_z<a_y\) 的 \(z\) 后退出,则从糖果 \(z\) 到糖果 \(n\) 任意一个的现价为 \(1\) 即可,同时易得现价全部为 \(2\) 的定价方案也可,则后面部分的方案数为 \(2^{n-z+1}\) 。
\(n^2\) 做法
易知随着 \(x\) 的增大, \(z\) 一定越来越小,则可以使用双指针维护 \(z\) 。
这对 \((x,y)\) 的方案数为\( \begin{pmatrix} x-2 \\ m-y-1 \end{pmatrix} \) \(\cdot\) \(2^{n-z+1}\) 。
最后用 \(2^n\) 减去计算出来的不合法的方案数即可。
时间复杂度 \(O(\sum n^2)\)
\(code:\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
int t;
int n,m;
int a[200010];
int c[10010][10010];
ll p2[10010];
void init(){
c[0][0]=1;
for(int i=1;i<=10000;++i){
c[i][0]=1;
}
for(int i=1;i<=10000;++i){
for(int j=1;j<=i;++j){
c[i][j]=c[i-1][j-1]+c[i-1][j];
c[i][j]%=mod;
}
}
p2[0]=1;
for(int i=1;i<=5000;++i){
p2[i]=p2[i-1]*2;
p2[i]%=mod;
}
return;
}
bool cmp(int a,int b){
return a>b;
}
int main(){
cin.tie(nullptr)->sync_with_stdio(false);
cin >> t >> t;
init();
while(t-->0){
cin >> n >> m;
for(int i=1;i<=n;++i){
cin >> a[i];
}
sort(a+1,a+n+1,cmp);
ll ans=p2[n];
for(int x=1;x<=n;++x){
int lst=n+1;
for(int y=x+1;y<=n;++y){
if(a[x]==a[y] || m-x-1<0){
continue;
}
if(a[y]*2<=a[x]){
break;
}
while(a[lst-1]+a[y]<a[x] && lst>1){
--lst;
}
if(m-x-1>y-2){
continue;
}
ans-=c[y-2][m-x-1]%mod*p2[n-lst+1]%mod;
ans%=mod;
if(ans<0){
ans+=mod;
}
}
}
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号