专栏文章
NOIP_2025_T2
算法·理论参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mimx4clv
- 此快照首次捕获于
- 2025/12/01 16:59 3 个月前
- 此快照最后确认于
- 2025/12/01 16:59 3 个月前
闲话
赛时 思考 的部分分,结果发现好像可以拓展到所有的 ,然后就花了 写了个假的写法,发现之后,又用了 ,去写正解,极限写完。
写法
曾经说过,一个好的题目,一定能从部分分来启发正解,对于这道题,我们首先把目光放在数据点7,8,9,14,15,发现一个共同点,居然是 !!!
对于只有两块钱的情况:
- 购买两个花费为 (或者一个,有可能不够)。
- 购买一个花费为 。
不妨设两个 的是 一个 的是 。
那么如果现在购买 ,不合法的情况也就是 ,但是我们有要先买 ,所以有
- 又有 所以 显然不合法,所以舍掉,因为 。
- 又有 所以 ,肯定可以,因为 。
现在购买 ,那么就是 并且 ,但是并不满足 ,所以舍掉。
综上,只有 并且 才是不行的方案
OHHHHHHHHH,我们居然退出来了 的办法,那我们试着推广一下?
那也就是 会被在性价比比 要大的那些正好卡到 才是不行的方案数,所以枚举以下就可以了! 成功拿到 ,我可真厉害。
正解(过了民间数据)
发现直接计算和发方案不太行,直接考虑容斥,我们只需要统计上述不合法方案数,使用 不合法方案数 就可以了。
为了更好理解些,直接浪费 的复杂度来枚举被卡掉的点 ,这样才好统计填补的点 。
将所有 从大到小排序,这时候我们会发现可以把其他的点都归类到三种集合中。
- 集合 ::
-
- 无论 是 1 还是 2,性价比都排在 的前面。这些糖果一定在 之前被考虑。
- 集合 并且 :
-
- 若 ,那么性价比比 要高,排在 的前面
- 若 ,那么性价比比 要低,排在 的后面
- 集合 并且 :
-
- 不管 是 1 还是 2,性价比都比 第,永远排在 的后面。
对于固定的 ,要使其成为因为没钱被调过的糖果,就用到了我们上面的理论——性价比比 高的正好花费了 元钱,包括 中的所有糖果,花费 ,以及 中 的糖果。
设 ,设 是 中所有 的个数, 是 中所有 的个数。
总花费 = 。
。
方案数变成了从 个位置中选出 个位置的方案数:
在 被跳过之后,只剩下一块钱, 中剩余的糖果一定是 ,(不然前面就已经选走了),买不起,跳过。
中的糖果按照顺序考虑
- 如果 ,那么买不起,跳过
- 遇到的第一个 糖果就是
- 如果遍历完也没看到 的,那么就没有
那我们找到了固定的 ,方案是不合法的仅当:已经存在购买过的一元糖果 ,使得 。
其中 可以来自 或者 。
对于所有满足 的时候选择 ,那么 必须选择 2(如果在 )或者不能选择 1(如果在 中,也就是必须是2)。
那我们可以记录一个阈值 ,设 中满足 的元素集合。
要避免交换,必须强制 中的所有元素 ,这回固定 中元素对 的贡献:
- 中的元素必须是 增加 。
- 中的元素必须是 其实就是不增加,因为 统计的是 。
剩余任意可选的:。
需要统计的方案数:。
方案数:
于是当前 的贡献:
其中 表示 之后的 中可以任意取的值。
复杂度
OHHHHHHHHHHHHHHHH!!!
切出来了!!!
(虽然用了 )
Code
CPP#include<bits/stdc++.h>
using namespace std;
/*
剩余2元时
只有可能1把更有的2给卡住
也就是当前1的最大值的性价比比2的最大值的性价比要大
1:x,y
2:z
y > z/2 && x + y < z
如果满足上述条件,那就是完damn了
剩余大于2元时
总是可以转换成2元的效果
考虑枚举z
O(n^2)可过吗??? sigma_n <= 5*10^4 应该差不多吧,?。万一数据卡掉那不就直接炸了。。。。。。
OOOOO 说了n <= 5000 ,所以应该差不多大概好像可以。
那么总合法方案数量就是总方案数(2^n) - 总不合法方案数
求一下上面那个条件就可以了,枚举?
欸?在上述中x可能不存在
现在枚举被卡掉的2
首先降序排序
当前第i个为ai
设A集合为所有的j且aj >= ai
设B集合为所有的j且j > i && 2aj >= ai(也就是选2排在i后,选1排在i前)
设C集合为所有的j且j > i && 2aj <= ai(也就是不论怎样都要在i的后面)
设Na = |A|,Nb = |B|,kA中取w=2的个数,kB为B中取w=1的个数。
总花费:(Na - kA) * 1 + kA * 2 + kB * 1 = Na + kA + kB
Na + kA + kB = m - 1 => kA + kB = m - 1 - Na
方案数为从 Na + Nb 个位置中选m - 1 - Na个位置贡献额外全值的方案数:
C_{m - 1 - Na}^{kA + kB}
在u被跳过后,剩下的钱只有1
B中剩余的糖果必然是w=2,否则会在u的前面买,那就没钱了
C中的糖果按照顺序考虑
如果也是w=2,买不起,跳过
遇到的第一个w=1的糖果也就是v
如果遍历完c也没有w=1的糖果,也就是v不存在
对于固定的uv,方案是坏的仅当:存在已经购买的一元糖果z,az < au + av
其中z可以来自AorB
所以对于当前uv的贡献,也就是 ([ka + kb,m - 1 - Na] - [(Na + Nb - |S|),(m - 1 - Na) - |A U S|]) * 2^c
*/
#define int long long//十年oi一场空,不开__见__
#define O(i,e,r) for (register int i = (e);i <= (r);++i)
#define U(i,e,r) for (register int i = (e);i >= (r);--i)
#define I(e,r) for (auto e : (r))
#define gc getchar
#define mul(a,b) a = (a * b) % mod
#define add(a,b) a = (a + b) % mod
#define sub(a,b) a = (a - b + mod) % mod
#define fi first
#define se second
const int mod = 998244353;
const int N = 200000;
int Case,T_T,n,m,tot;
int a[N + 5],fact[N + 5],inv[N + 5],finv[N + 5],qp2[N + 5];
inline void read(int &x){
x = 0;
int f = 1;
char c = gc();
while (c < '0' || c > '9'){
if (c == '-') f = -1;
c = gc();
}
while (c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c - 48);
c = gc();
}
x *= f;
}
inline bool cmp(int x,int y){return x > y;}
inline int qpow(int x,int y){
int base = x,ans = 1;
while (y){
if (y & 1) mul(ans,base);
mul(base,base);
y >>= 1;
}
return ans;
}
inline int Cc(int x,int y){
if (y < 0 || y > x) return 0;
return fact[x] * finv[y] % mod * finv[x - y] % mod;
}
inline void init(){
fact[0] = finv[0] = qp2[0] = 1;
O(i,1,N){
fact[i] = fact[i - 1] * i % mod;
finv[i] = qpow(fact[i],mod - 2);//超慢逆元
qp2[i] = qp2[i - 1] * 2 % mod;
}
/*
ai + b = 0
a * invi * i * invb + invi * invb * b = 0
a * invb + invi = 0
invi = -b * inva
*/
}
signed main(){
//zcq_qwq AK IOI !!!!!! zcq巨佬保我这道题AC!!!!!
// freopen("sale.in","r",stdin);
// freopen("sale.out","w",stdout);
read(Case), read(T_T);
init();
while (T_T--){
read(n), read(m), tot = 0;
O(i,1,n) read(a[i]);
sort(a + 1,a + n + 1,cmp);
O(i,1,n){
int idx = i + 1;
while (idx <= n && 2 * a[idx] > a[i]) ++idx;//寻找B集合的
int A = i - 1,B = idx - 1 - i,C = n - idx + 1;//A,B,C集合的
int tmp = Cc(A + B,m - 1 - A);
// cout << A << ' ' << B << ' ' << C << ' ' << idx << ' ' << tmp << '\n';
if (tmp == 0) continue;
vector <pair<int,int> >vec;
int x1 = 1,x2 = i + 1;
while (x1 < i && x2 < idx){//双指针
if (a[x1] >= a[x2]){
vec.push_back({a[x1],0});
++x1;
}else{
vec.push_back({a[x2],1});
++x2;
}
}//0表示A集合的,1表示B集合的
while (x1 < i){
vec.push_back({a[x1],0});
++x1;
}
while (x2 < idx){
vec.push_back({a[x2],1});
++x2;
}
int k = vec.size() - 1,kA = 0,kB = 0;
O(j,0,C){
int val = 0;
if (j < C) val = a[idx + j];
int D = a[i] - val;
// cout << a[i] << ' ';
// cout << val << ' ' << D << ' ' << k << '\n';
while (k >= 0 && vec[k].fi < D){
if (vec[k].se == 0) ++kA;
else ++kB;
--k;
} //统计vec里面的A,B元素
int b = (tmp - Cc(A + B - kA - kB,m - 1 - A - kA) + mod) % mod;
if (b > 0){
if (j < C) tot = (tot + b * qp2[C - 1 - j]) % mod;
else tot = (tot + b) % mod;
}
}
// cout << tot << '\n';
}
cout << (qp2[n] - tot + mod) % mod << '\n';
}
}
总结
是个题目,并且大于等于红,小于等于黑。
Q_Q 真的不是我用 卡常数,不然会真的 TLE。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...