牛客 周赛99 20250712

牛客 周赛99 20250712

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

A:

void solve(){
	string s;
	cin>>s;
	for (int i=0;i<s.size()-1;i++){
		if (s[i]=='9'&&s[i+1]=='9'){
			cout<<"YES"<<endl;
			return;
		}
	}
	cout<<"NO"<<endl;
}

签到

B:

题目大意:给出一格字符串,判断至少需要多长的 ASCII 码表才能完整输出这些字符

void solve(){
	int n;
	cin>>n;
    int ans=0;
	for (int i=1;i<=n;i++){
        char c;
        cin>>c;
        ans=max(ans,(int)c);
    }
    cout<<ans<<endl;
}

记录 ASCII 码最大的字符即可

C:
题目大意:

image-20250711200018821

void solve(){
	int k;
	cin>>k;
	if (k==0){
		cout<<1<<endl;
		return;
	}
	if (k&1){
		cout<<4;
		for (int i=1;i<=k/2;i++) cout<<8;
	}else{
		for (int i=1;i<=k/2;i++) cout<<8;
	}
	cout<<endl;
}

贪心,如果能在当前位放 \(8\) ,则放 \(8\) 是最优的

因为前导 \(0\) 不参与计算,所以当洞数为奇数时,首位放 \(4\) 一定优,其余位则放 \(8\) ,用更少的数累加得到更多的洞数

特别的,当 \(k=0\) 时,特判为 \(1\)

D:

题目大意:

image-20250711200409499

void solve(){
	LL x,p;
	cin>>x>>p;
	
	if (x==1){
		cout<<2*p-1<<endl;
		return;
	}
	if (p<x){
		cout<<2*p<<endl;
		return;	
	}else{
		if (p%x==0)
			cout<<p/x*2-1<<endl;
		else
			cout<<2*((x-1)*(p/x)+p%x)<<endl;
	}
}

可以观察到这样一个性质:

\(k\%x=0\) 时,这一位的元素才为奇数,否则都为偶数,这是因为当且仅当 \(k\%x=0\)\(\lfloor\frac{k}{x}\rfloor\) 的奇偶性才发生改变,且这时的前缀和的奇偶性也发生改变,那么只有这一位为奇数,\(\lfloor\frac{k}{x}\rfloor\) 分块内的元素为偶数才满足

所以可以根据周期性来计算 \(p\) 位上的数

  • \(p<x\) 时,第一段分块内的数都是偶数

  • \(p\ge x\) 时,判断是否在分界点上(奇数),否则计算偶数的个数

    \[a_p=2\times (x-1) \times(\lfloor\frac{p}{x}\rfloor)+p\%x \]

  • 特别的,当 \(x=1\) 时,数组全为奇数

E:
题目大意:

image-20250711204859683

int a[200010];

void solve(){
	int n;
	cin>>n;
	for (int i=1;i<=n;i++) cin>>a[i];
	map<int,int> mp;
	for (int i=1;i<=n;i++) mp[a[i]]++;
	int r=n;
	a[n+1]=1e9+1;
	while (r>=1&&a[r]>=r&&a[r]<a[r+1]&&mp[a[r]]==1) r--;
	set<int> st;
	for (int i=1;i<=r;i++)
		if (a[i]!=i) st.insert(a[i]);
	cout<<st.size()<<endl;
}

如果存在一段连续严格单调递增的前缀串,当到某一位出现 \(a_i\le a_{i-1}\) 时,区间 \([1,i]\) 上,所有的数都需要被修改,且修改后 \(a_i=i\)

所以可以找到一个分界点,分界点左边的元素需要修改为 \(a_i=i\) 右边的数则不需要修改

所以从后往前遍历找分界点 \(r\) ,当满足 \(a_r\ge r\)\(a_r<a_{r+1}\) 时才能向左扩展

需要注意的是,如果 \(a_r\)\(r\) 之前已经出现过一次了,那么就需要停止

image-20250711210229238

将元素大小转化成折线图,当一段区间的左右端点值相同时,无论中间的情况如何,这一段区间一定需要修改

while (r>=1 && a[r]>=r && a[r]<a[r+1] && mp[a[r]]==1) r--;

找到分界点后,遍历这一段前缀区间,需要修改的次数为 \(a_i\ne i\) 的个数

F:

题目大意:\(n\) 个糖果分给 \(m\) 个人,计算最后所有人分到的糖果数最大按位与的数值

void solve(){
	LL n,m;
	cin>>n>>m;
	LL ans=0;
	
	if (m==1){
		cout<<n<<endl;
		return;
	}
	
	for (int i=30;i>=0;i--){
		if (n>=m*(1<<i)){
			ans+=(1<<i);
			n-=m*(1<<i);
		}else if (n>=m*((1<<i)-1)){
			LL k=(n-m*((1<<i)-1)+(1<<i)-1)/(1<<i);
			n-=k*(1<<i);
		}
	}
	cout<<ans<<endl;
}

从最高位到最低位贪心,存在三种情况:

  • 当前的 \(n\) 比这一位 \(i\) 全放 \(1\) 的数值还大,那么这一位就可以填 \(1\)

  • 当前的 \(n\) 比这一位 \(i\) 全放 \(1\) 小,但是比 \(i-1\) 之后的所有位都放 \(1\) 还大,如果后面全放 \(1\) ,那么最后必然会剩下某些糖果,所以在 \(i\) 位上需要放某些 \(1\) 使得部分糖果浪费掉(必要的牺牲)

    \[k=\lceil \frac{n-m*(2^i-1)}{2^i} \rceil \]

    需要在这一位填 \(1\) 的个数是上取整的

  • 当前的 \(n\)\(i-1\) 位之后所有位都放 \(1\) 还小,那么就重新考虑 \(i-1\) 的情况

posted @ 2025-07-13 19:33  才瓯  阅读(10)  评论(0)    收藏  举报