牛客 周赛94 20250531

牛客 周赛94 20250531

https://ac.nowcoder.com/acm/contest/110696

A:
题目大意:

void solve(){
	int n;
	cin>>n;
	cout<<(n+1)/2<<endl;
}

签到

B:
题目大意:

int a[4],b[4],n;
int ans;
bool st[4];

void dfs(){
	ans=max(0,min(ans,n));
	for (int i=1;i<=3;i++){
		if (a[i]<=n&&st[i]==0){
			st[i]=1;n-=b[i];
			dfs();
			st[i]=0;n+=b[i];
		}
	}
}

void solve(){
	cin>>n;
	ans=n;
	for (int i=1;i<=3;i++) cin>>a[i]>>b[i];
	dfs();
	cout<<ans<<endl;
}

DFS暴力搜一次即可,时间复杂度为 \(O(T\cdot2^3 )\)

C:
题目大意:

void solve(){
	int x;
	cin>>x;
	if ((x&(x-1))==0) cout<<-1<<endl;
	else cout<<(1<<(31-__builtin_clz(x)))<<endl;
}

数学题,\(x\&y\) 存在约束有: \(x\&y<x,x\&y<y\) ,需要构造严格小于 \(x\)\(y\) 且能构造出三角形,所以 \(y+x\&y>x\)

移项后可以得到 \(y\) 的下界:\(y>x-x\&y>x-y\iff 2y>x\)

任意的正整数 \(x\) 转化为二进制后必然在某一位上存在一个 \(1\),记最高位的 \(1\) 在第 \(a\) 位上

那么 \(y\) 至少在第 \(a-1\) 位上存在一个 \(1\),至多在第 \(a\) 位上存在一个 \(1\) ,现在考虑 \(x\&y\) 的关系:

假设 \(x\) 只在 \(a,a-1\) 位上存在 \(1\),当 \(y\) 只在 \(a-1\) 位上有 \(1\)\(x\&y\) 也只存在一个 \(1\)\(a-1\) 位上,显然 \(y+x\&y<x\)

同理可以推出:任意的 \(x\) 的最高位 \(1\)\(a\) 位上,除非 \(y\) 也在 \(a\) 位上有 \(1\) ,否则不存在满足不等式的解

又因为 \(y\)\(x\) 都在 \(a\) 位上有 \(1\),所以 \(y>(x>>1)\)\(x\&y=y>(x>>1)\) \(>>\) 表示右移位操作)

所以对任意的 \(x\) 都构造一个在第 \(a\) 位有 \(1\) ,其他位为 \(0\)\(y\) ,是最小的构造方案

特别的,当 \(x\)\(2\) 的整数次幂时,\(x\) 有且仅有一位 \(1\),这时的 \(y=x\) 需要排除

*:__builtin_clz(x) 内置函数用来计算 \(x\) 的前置 \(0\) 的数量

D:
题目大意:

void solve(){
	int n;
	cin>>n;
	string s;
	cin>>s;
	if (s.back()=='1') cout<<"NO"<<endl;
	else{
		int cnt=0;
		for (auto c:s){
			if (c=='1') cnt++;
			else if (cnt==1){
				cout<<"NO"<<endl;
				return;
			}
		}
		cout<<"YES"<<endl;
	}
}

显然,当字符串最后一个元素为 \(1\) 时,一定不存在对应的构造方式,并且前缀 \(0\) 无影响,可以忽略

那么能构造出的字符串可以被表示为 \(1\cdots0\) ,即第一位和最后一位分别为 \(1,0\) ,现在考虑中间元素的构造方案

可以观察到,\(1\cdots0\) 可以先被涂成 \(111\cdots0\) ,除了最后一位其他都是 \(1\)那么中间的 \(0\) 可以一个一个的涂上去

特别的,如果需要涂的中间的 \(0\) 在第二位上,即 \(1011\cdots0\) ,不存在构造方式

除此之外,剩余的 \(s^{\prime}\) 都可以先去除前缀 \(0\),再涂成形如 \(111\cdots0\) 的形式,再将中间的 \(0\) 一个一个涂上去构造出来

E:
题目大意:

void solve(){
	int n,k;
	cin>>n>>k;
	LL ans=k+1;
	int cnt=0;
	while (n&&cnt<k){
		if (n&1 && n!=1) ans+=k-cnt-1;
		n>>=1;
		ans++;
		cnt++;
	}
	cout<<ans<<endl;
}

将乘除 \(2\) 的操作视为位运算的左右移,将 \(n\) 左移相当于在 \(n_{(2)}\) 后补 \(0\)\(n\) 右移相当于消去 \(n_{(2)}\) 的最低位

\(n\) 的最低位是 \(1\) 时,如果将 \(n\) 右移再左移那么这一位上便会损失掉 \(1\),这是一个有效增加不同数的方式

\(n\) 的最低位时 \(0\) 时,先左移再右移或先右移再左移不会产生实质性的变化,因此只需要记录一次即可

所以答案的统计分为两部分:\(k+1\) 个通过左移产生的数,以及通过某些除的步骤再得到的数

if (n&1 && n!=1) ans+=k-cnt-1;

\(n\) 的最低位是 \(1\) 时,如果将 \(n\) 右移,剩余的步数都左移,则会得到 \(k-cnt-1\) 个新产生的数字,这些数字一定是唯一得到的

\(n\) 的最低位是 \(1\) 时,将 \(n\) 右移不会导致 \(1\) 的流失,所以只需要记录一次新产生的 \(n/2\)

F:
题目大意:

const LL mod=998244353;

LL mem[5010][5010];

LL ksm(LL a,LL b,LL p){
	LL res=1;
	while (b){
		if (b&1) res=(res*a)%p;
		a=(a*a)%p;
		b>>=1;
	}
	return res;
}

LL cal(LL n,LL k){
	if (mem[n][k]) return mem[n][k];
	k=min(k,n-k);
	LL sumn=1,sumk=1;
	for (int i=1;i<=k;i++){
		sumn=(sumn*(n-i+1))%mod;
		sumk=(sumk*i)%mod;
	}
	mem[n][k]=sumn*ksm(sumk,mod-2,mod)%mod;
	return mem[n][k];
}

void solve(){
	int n;
	cin>>n;
	vector<int> a(n);
	for (auto &i:a) cin>>i;
	unordered_map<int,int> mp;
	for (auto i:a) mp[i]++;
	LL ans=0;
	for (int i=1;i<=n;i++) ans=(ans+(i+1)/2*cal(n,i)%mod)%mod;
	for (auto [k,v]:mp){
		for (int i=1;i<=v;i++){
			for (int j=0;j<=min(n-v,i-1);j++){
				ans=(ans-(i+j+1)/2*cal(v,i)%mod*cal(n-v,j)%mod+mod)%mod;
				ans=(ans+i*cal(v,i)%mod*cal(n-v,j)%mod)%mod;
			}
		}
	}
	cout<<ans<<endl;
}

分组的数量取决于子集中不同颜色的球的数量,可以分为两类进行讨论:

  • \(f(\mathbb{S})=\lceil sum/2\rceil,sum=\lvert \mathbb{S}\rvert\):所有的球都能两两配对,剩余的单独球自己为一组
  • \(f(\mathbb{S})={\rm{max}}(\vert \mathbb{s}\rvert)\)\(\vert \mathbb{s}\rvert\) 表示集合 \(\mathbb{S}\) 中颜色为 \(\mathbb{s}\) 的球数量:将颜色最多的球与其他的球配对,剩余的自己为一组

判断 \(f(\mathbb{S})\) 的依据在于 \({\rm{max}}(\vert \mathbb{s}\rvert)\) 的数量,如果 \({\rm{max}}(\vert \mathbb{s}\rvert)>\lceil sum/2\rceil\)说明 \(\mathbb{s}\) 对应的颜色为绝对众数,他能够把 \(sum-{\rm{max}}(\vert \mathbb{s}\rvert)\) 这么多个球和其他颜色的球都配上对,剩余的球则自己为一组,这样对答案的贡献为 \({\rm{max}}(\vert \mathbb{s}\rvert)\)

*绝对众数在一个集合中,如果一个元素的出现次数比其他所有元素的出现次数之和还多,那么就称它为这个集合的绝对众数


因为对每一种颜色的球都进行讨论较为复杂,所以可以先将答案设置为全部由 \(\lceil sum/2\rceil\) 产生的,后面再对每一种颜色的贡献进行更新

那么一开始就先计算每一个长为 \(i\) 的子集以 \(\lceil sum/2\rceil\) 方式产生的贡献

\[ans=\sum_{i=1}^n \lceil \frac{i}{2}\rceil\cdot\binom{i}{n} \]

然后在记录的桶中对每一种颜色进行贡献更新:

定义当前枚举的颜色球数量为 \(i\) ,在绝对众数的情况下,其余颜色球的数量为 \(j\) 且上界为 \({\rm{min}}(n-i,i-1)\)

首先需要减去这种组合在 \(\lceil sum/2\rceil\) 下的贡献

\[ans\longleftarrow ans- \sum_{i=1}^v\sum_{j=0}^{{\rm{min}}(n-i,i-1)} \lceil\frac{i}{2}\rceil\cdot\binom{i}{v}\cdot\binom{j}{n-v} \]

将这种子集看作两部分,一部分为选出的 \(i\) 个绝对众数的球,另一部分为剩余不超过绝对众数的球,组合数为 \(C_v^i\cdot C_{n-v}^j\)

然后加上在 \({\rm{max}}(\vert \mathbb{s}\rvert)\) 下的贡献

\[ans\longleftarrow ans+\sum_{i=1}^v\sum_{j=0}^{{\rm{min}}(n-i,i-1)}i\cdot\binom{i}{v}\cdot\binom{j}{n-v} \]


优化处理

LL mem[5010][5010]

LL cal(LL n,LL k){
	if (mem[n][k]) return mem[n][k];
	k=min(k,n-k);
	LL sumn=1,sumk=1;
	for (int i=1;i<=k;i++){
		sumn=(sumn*(n-i+1))%mod;
		sumk=(sumk*i)%mod;
	}
	mem[n][k]=sumn*ksm(sumk,mod-2,mod)%mod;//计算分母的逆元
	return mem[n][k];
}

计算组合数可以进行记忆化,平摊时间复杂度会降低一半,需要注意的是,在模意义下的除法需要被替换为逆元表示

posted @ 2025-05-31 15:31  才瓯  阅读(19)  评论(0)    收藏  举报