[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;
}
posted @ 2025-12-02 20:22  wlh__1  阅读(343)  评论(0)    收藏  举报