牛客 周赛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\) 方式产生的贡献
然后在记录的桶中对每一种颜色进行贡献更新:
定义当前枚举的颜色球数量为 \(i\) ,在绝对众数的情况下,其余颜色球的数量为 \(j\) 且上界为 \({\rm{min}}(n-i,i-1)\)
首先需要减去这种组合在 \(\lceil sum/2\rceil\) 下的贡献
将这种子集看作两部分,一部分为选出的 \(i\) 个绝对众数的球,另一部分为剩余不超过绝对众数的球,组合数为 \(C_v^i\cdot C_{n-v}^j\)
然后加上在 \({\rm{max}}(\vert \mathbb{s}\rvert)\) 下的贡献
优化处理
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];
}
计算组合数可以进行记忆化,平摊时间复杂度会降低一半,需要注意的是,在模意义下的除法需要被替换为逆元表示