CF2182F1/2超能🦌战队(Christmas Reindeer)题解

超能 🦌 战队 题解

思路

F1 和 F2 还是有区别的,这取决于思考问题是从具体的鹿开始还是鹿的力气开始,但从后者开始反而更简单……

全是 2 的次幂,肯定要从二进制开始考虑啊。观察一下就可以发现,题面 \(c_1'+\lfloor \frac{c_2'}{2}\rfloor+\lfloor \frac{c_2'}{4}\rfloor+\cdots\) 这种奇怪的设定实际上是为了防止进位,如果我们先后选了 \(i\)\(j\) 两头鹿(中间没选),那么它们的贡献在负载能力的二进制位中间就隔了 \(c_i-c_j\)\(0\)

\(x\) 的二进制上考虑,如果 \(x\) 的一位是 \(0\),而负载能力这一位却是 \(1\),后面怎么样就可以随便选了。而如果反过来,后面无论怎么选也凑不够(因为没有进位),所以我们要找到一个大于等于 \(x\) 的最小负载能力,而对应的选鹿方案我们称之为“最小方案”。

set 维护当前存在的鹿的力量值和同力量值鹿的数量,从大到小选鹿,如果不够就在前面多选一个结束选择。记最小方案中力量值 \(i\) 使用了 \(cnt_i\) 个,而 \(c_j=i\) 的鹿的数总量记为 \(k_i\)

如果我们选的最小负载能力是严格等于 \(x\) 的,对于力量值 \(i\),它至少选 \(cnt_i\) 个,但有 \(k_i\) 个,那么就有 \(\sum_{j=cnt_i+1}^{k_i}\binom{k_i}{j}\) 种选法是可以满足比 \(x\) 大所以后面随便选的,记力量值比 \(i\) 小的驯鹿有 \(suf_i\) 头,力量值比 \(i\) 大的驯鹿在最小方案中的选法有 \(pre_i\) 种,则方案数为:

\[2^{suf_i}pre_i\sum_{j=cnt_i+1}^{k_i}\binom{k_i}{j} \]

而如果我们选的最小负载能力是严格大于 \(x\) 的,假设从高位到低位比较时,第一次使结果严格大于 \(x\) 的那一位是由力量为 \(p\) 的鹿贡献的,那么这一位的循环下界应该是 \(cnt_p\),而后面不用再计算(因为我们这个式子是在保证大于 \(x\) 时后面可以随便选,而 \(p\) 的存在本身已经满足这个条件了,就不需要同过多选一个满足大于的需求,而后面也已经被这一位计算)。

对于 hard version\(k_i\) 可能很大,但注意到 \(\sum cnt_i\leq 60\),所以这只需要一个二项式定理即可解决:

\[\sum_{j=cnt_i+1}^{k_i}\binom{k_i}{j}=2^{k_i}-\sum_{j=0}^{cnt_i}\binom{k_i}{j} \]

受限制的方案数就是 \(pre_i=\prod_{j=i+1}^{60}\binom{k_j}{cnt_j}\),最后别忘加上什么也不多选的最小方案的方案数,也就是 \(pre_{-1}\)

AC code

#include <bits/stdc++.h>
#define int int64_t
//#define int __int128
#define MOD (998244353)
//#define eps (1e-6)
#define endl '\n'
#define debug_endl cout<<endl;
#define debug cout<<"debug"<<endl;
namespace Easy_Count{
	const int _MAXN=6e5+10;
	int inv[_MAXN],jc[_MAXN];
	template<typename T>
	inline int mod_plus(T val) { return val % MOD; }
	template<typename T, typename... Rest>
	inline int mod_plus(T first, Rest... rest) {
		return (static_cast<long long>(first % MOD) + mod_plus(rest...)) % MOD;
	}
	template<typename T>
	inline int mod_mul(T val) { return val % MOD; }
	template<typename T, typename... Rest>
	inline int mod_mul(T first, Rest... rest) {
		return (static_cast<long long>(first % MOD) * mod_mul(rest...)) % MOD;
	}
	inline int mod(int x){
		return (x%MOD+MOD)%MOD;
	}
	inline int qpow(int a,int b){
		int res=1;
		while(b){
			if(b&1){
				res=(res*a)%MOD;
			}
			a=(a*a)%MOD;
			b>>=1;
		}
		return res;
	}
	inline int C(int n,int m){
		return mod_mul(jc[n],inv[n-m],inv[m]);
	}
	inline void init_binom(int n){
		jc[0]=1;
		for(int i=1;i<=n;++i){
			jc[i]=(jc[i-1]*i)%MOD;
		}
		inv[n]=qpow(jc[n],MOD-2);
		for(int i=n-1;i>=0;--i){
			inv[i]=(inv[i+1]*(i+1))%MOD;
		}
	}
}
using namespace Easy_Count;
using namespace std;
struct Node{
	int v;
	mutable int k;
	Node(){}
	Node(int _v,int _k):v(_v),k(_k){}
	bool operator<(const Node& other)const {
		return v>other.v;
	}
};
int n,m,sum[600010];
set<Node> s;
int solve(int x){
	int res=0;
	if(s.empty())return 0;
	vector<int> tx;
	while(x){
		tx.emplace_back(x&1);
		x>>=1;
	}
	vector<Node> tmp;
	vector<int> cnt(s.size());
	for(Node p:s) tmp.push_back(p);
	int j=0,c=0,endi=-1;
	bool finish=false;
	for(int i=tx.size()-1;i>=0;--i){
		if(tx[i]==1){
			while(j<tmp.size()&&tmp[j].v-c>i){
				++j;
			}
			if(j<tmp.size()&&tmp[j].v-c==i){
				if(tmp[j].k==cnt[j]){//不够了
					endi=j-1;
					cnt[j]=0;
					break;
				}
				++cnt[j];
			}
			else{//不能刚好凑出
				endi=j-1;
				break;
			}
			++c;
		}
		if(i==0) finish=true;//完整跑完了,不需要往前找
	}
	int p=-1;
	if(!finish){
		for(int i=endi+1;i<tmp.size();++i) cnt[i]=0;
		for(int i=endi;i>=0;--i){
			if(tmp[i].k>cnt[i]){
				++cnt[i];
				p=i;
				finish=true;
				break;
			}
		}
	}
	if(!finish) return 0;
	int suf=0,pre=1;
	c=0;
	for(int i=0;i<tmp.size();++i) suf+=tmp[i].k;
	for(int i=0;i<tmp.size();++i){
		suf-=tmp[i].k;
		c+=cnt[i];
		int sum=0;
		for(int j=0;j<=(i==p?cnt[i]-1:cnt[i]);++j) sum=mod_plus(sum,C(tmp[i].k,j));//∑cnti不会超过60
		sum=mod(qpow(2,tmp[i].k)-sum);
		res=mod_plus(res,mod_mul(qpow(2,suf),pre,sum));
		pre=mod_mul(pre,C(tmp[i].k,cnt[i]));
		if(i==p) break;
	}
	if(p==-1)res=mod_plus(res,pre);
	return res;
}
signed main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	init_binom(6e5);
	for(int i=1;i<=n;++i){
		int x;
		cin>>x;
		auto it=s.find(Node(x,0));
		if(it==s.end()) s.emplace(x,1);
		else ++it->k;
	}
	for(int i=1;i<=m;++i){
		int opt;
		cin>>opt;
		if(opt==1){
			int x;
			cin>>x;
			auto it=s.find(Node(x,0));
			if(it==s.end()) s.emplace(x,1);
			else ++it->k;
		}
		else if(opt==2){
			int x;
			cin>>x;
			auto it=s.find(Node(x,0));
			if(it->k==1) s.erase(it);
			else --it->k;
		}
		else{
			int x;
			cin>>x;
			cout<<solve(x)<<endl;
		}
	}
	return 0;
}
posted @ 2026-08-21 18:35  司马只因锥  阅读(3)  评论(0)    收藏  举报