P16434 [APIO 2026 中国赛区] 蛋糕 个人题解

场切了,感觉题有点难评,遂来写题解。

按我场上的思考顺序写。

下文使用 \(x\) 来指代 Sally 的蛋糕。

Subtask 2

注意到 \(1 \le d_i \le 3\) , 并且只有一次询问机会,容易想到需要用三种结果对应三种不同的 \(d_i\) , 具体构造很多,我采用的是加入两个美味度为 \(1\) 的蛋糕,然后询问 \([0,1]\) 与 \([2]\) ,注意因为 \(1 \le d_i\) ,所以我们可以把最美味的蛋糕视为 \(x\) 。

若返回 \(1\) , 则说明 \(d=a_2=1\) 。
若返回 \(0\) , 则说明 \(d=a_2=2\) 。
若返回 \(-1\) , 则说明 \(d=a_2=3\) 。

Subtask 1

考虑扩展 Subtask 2 的做法,可以向 \(a\) 中加入 100 个 \(1\) ,这样 \(x\) 就可视为 \(a_{100}\),又注意到 \(W=K=100\) ,则可以一次询问排除一个数。

具体来说,对于 \(i \in [1, 100]\) ,我们将 \([0,i-1]\) 中的每个数加入 \(S_1\) ,然后将 \(100\) 加入 \(S_2\) ,若询问返回 \(0\) ,则 \(x=i\) 。

注意到此过程可以通过二分进行优化,但在 Subtesk 2 中不必要。

Subtask 3

注意到 \(K=30\) 且 \(W=10^9\) ,即 \(2^K \approx W\) ,很难不想到与二分或倍增有关,发现 \(N=40\) 略大于 \(30\) ,则自然想到向 \(a\) 中添加 \(2\) 的若干次幂。

此时发现性质:\(a_{i} - 1 = a_0 + a_1 + \cdots + a_{i-1}\) ,即 \(a\) 中前若干个数的和等于下一个数减一。我们可以考虑再向 \(a\) 中加入一个 \(1\) ,这样这个式子就变成了 \(a_{i} = a_0 + a_1 + \cdots + a_{i-1}\) ,对应到询问就是 \(compare\_tastiness([0, 1,\cdot \cdot \cdot ,i-1], [i]) = 0\) 。下面称 \(compare\_tastiness([0, 1,\cdots ,i-1], [i]) = 0\) 为询问 \(i\) 。

考虑加入 \(x\) 后会对询问 \(i\) 产生什么影响,若 \(x\) 的插入位置在 \(a_i\) 之后即 \(x \ge a_i\) (相等视为插在其后,不影响正确性) ,下标 \(i\) 之前的下标不受影响,则该询问返回值仍为 \(0\) 。若 \(x\) 的插入位置在 \(a_i\) 之前即 \(x \le a_i\) ,手玩可以发现询问 \(i\) 返回值一定为 \(1\) 。 于是我们有了一个判断 \(x\) 二进制下最高位 \(1\) 的位置的方法,同时我们也知道了 \(x\) 插入的位置。

实际操作就是从大到小进行判断,先判断 \(x\) 是否大于 \(2^{29}\) ,然后判断 \(x\) 是否大于 \(2^{28}\) ,以此类推,得到第一个 \(1\) 后就停止询问。设 \(2^b \le x < 2^{b+1}\) ,然后将 \(2^b\) 永久加入 \(S_1\) ,将 \(x\) 永久加入 \(S_2\) (此时 \(x\) 的位置为 \(b+1\))。这等效于仅 \(s_2\) 中有值为 \(x-2^b\) 的数,然后可以用 \(2^b\) 以前的数拼出来一个大于等于 1 ,小于等于 \(2^b\) 的数,以此来对 \(x-2^b\) 进行二分查找,最后还原 \(x\) 。

需要注意实现,否则容易超出次数限制。

Subtask 4

发现 \(W\) 的限制缩小到了 2000 ,\(N\) 的限制放大到了 3000 ,但 \(K\) 只为 7 。如果按照二分写,那么询问次数不可能小于10次,所以正解一定不是二分,发现在前面的二分中,我们最多利用两个返回值,能不能同时利用上三种不同的返回值,考虑到 \(3^7\) 略大于 \(W\) ,说明这个想法方向对了。

考虑到 \(N>W\) ,自然想到将 \(1,2,\cdots,2000\) 加入 \(a\) (为了下面叙述方便,也为了下面的一些做法,可以多加入 1 个 \(1\) ,使得下标 0 上的数字为 \(1\) ,其余数字与下标均相等) ,前三个 Subtask 都是先确定了 \(x\) 的位置再求解的,但 Subtask 4 只有 7 次询问机会,并没有多余次数让我们先找到位置再求 \(x\) ,但是因为我们向 \(a\) 中加入的数字是连续的,所以找到 \(x\) 的位置等效于找到 \(x\) 的值。

再思考插入 \(x\) 后的 \(a\) ,里面是包含 \(1\) 到 \(2000\) 的一个从小到大的数列,其中有一个数字出现了两次,问题转化为要找出这个重复的数字。

这个问题看起来很经典,沿用 Subtask 3 的思路,如何在一次询问内判断前 \(i\) 个数有没有重复的数,容易想到对于数列 \([1,2,3,\cdots,i]\) ,\(1+i\)等于两个中位数的和 (如果中位数只有一个,则可以用中位数与中位数-1,然后带上下标 0 上的 \(1\) ) ; 但是若 \(1 \le x<v_1\) ,数列将会变为 \([1,2,3,\cdots,x,x,\cdots,i-1]\) ,发现首项加末项相较原来减少了一,中位数之和较原来减少了二,则\(compare\_tastiness([1,i], [ \left\lfloor \frac{i}{2} \right\rfloor ,\left\lceil \frac{i+1}{2} \right\rceil , (0)])\)返回值会为 \(1\) ,同理可得当 \(v_2 \le x <i\) 时,返回值为 \(-1\),当 \(x \ge i\) 时,返回值为 \(0\) 。但是有问题,当 \(x=v_1\) 时,返回值也为 \(0\) ,这个做法倒闭了。并且这个做法只能做到 10 次询问。

考虑解决询问次数问题,我们可以让中间的两个数各向两侧移动相同步数,这样它们的和仍不变,这样整个数列就被分成了三个部分。
设当前 \(x \in [L,R]\) ,令 \(i=R+1\) ,这样解决了返回值为 \(0\) 时区间可能不连续的问题 。
令 \(S_1=\{ L,R \}\) , \(S_2=\{ mid_1,mid_2\}\) 。
注意当 \(L+R \neq mid_1+mid_2\) 时,可以向其中一个加入若干个值为 \(1\) 的下标使得它们在没有 \(x\) 影响时和相等。
分析得:
若 \(L \le x < mid_1\),询问 \((s_1,s_2)\) 的返回值为 \(1\) 。
若 \(mid_1 \le x < mid_2\),询问 \((s_1,s_2)\) 的返回值为 \(0\) 。
若 \(mid_2 \le x < R+1\),询问 \((s_1,s_2)\) 的返回值为 \(-1\) 。

于是就做完了,实现三分时需要精细,否则容易多一次询问导致挂 11 分 。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int compare_tastiness(std::vector<int> S1, std::vector<int> S2);
int a[20010],a_=0;
ll p2[70];
std::vector<int> bake_cakes(int N, int W, int K){
	if(W==100){
		vector<int> v;
		for(int i=1;i<=100;++i){
			v.push_back(1);
		}
		return v;
	}
	if(W==3){
		vector<int> v;
		v.push_back(1);
		v.push_back(1);
		return v;
	}
	if(W==1000000000){
		vector<int> v;
		v.push_back(1);
		for(int i=1;i<=1000000000;i*=2){
			v.push_back(i);
		}
		return v;
	}
	if(W==2000){
		vector<int> v;
		v.push_back(1);
		for(int i=1;i<=2001;++i){
			v.push_back(i);
		}
		for(int i=1;2002+i<=2200;i*=2){
			v.push_back(2002);
			v.push_back(2002+i);
		}
		for(int i=1;i<=15;++i){
			v.push_back(2002);
			v.push_back(2002+128);
		}
		return v;
	}
}

int find_tastiness(int m, int W, int K){
	if(W==100){
		vector<int> v1,v2;
		v2.push_back(100);
		for(int i=1;i<=100;++i){
			v1.push_back(i-1);
			int tmp=compare_tastiness(v1,v2);
			if(tmp==0){
				return i;
			}
		}
	}
	if(W==3){
		vector<int> v1,v2;
		v1.push_back(0);
		v1.push_back(1);
		v2.push_back(2);
		int ttmp=compare_tastiness(v1,v2);
		if(ttmp==-1){
			return 3;
		}
		if(ttmp==0){
			return 2;
		}
		if(ttmp==1){
			return 1;
		}
	}
	if(W==1000000000){
		p2[0]=1;
		for(int i=1;i<=40;++i){
			p2[i]=p2[i-1]*2;
		}
		vector<int> v1,v2;
		ll x=0;
		int ii=-1;
		int l=1,r=1000000000;
		for(int i=30;i>=1;--i){
			v1.clear();
			v2.clear();
			for(int j=0;j<i;++j){
				v1.push_back(j);
			}
			v2.push_back(i);
			int ttmp=compare_tastiness(v1,v2);
			if(ttmp==1){
				l=1;
				r=p2[i-1]-1;
			}else{
				l=p2[i-1];
				ii=i+1;
				break;
			}
		}
		while(l<r){
			v1.clear();
			v2.clear();
			int mid=l+(r-l)/2;
			int cha=mid-p2[ii-2];
			v1.push_back(ii-1);
			v2.push_back(ii);
			for(int i=ii-2;i>=1;--i){
				if(cha>=p2[i-1]){
					v1.push_back(i);
					cha-=p2[i-1];
				}
			}
			int ttmp=compare_tastiness(v1,v2);
			if(ttmp==1){
				r=mid-1;
			}else if(ttmp==-1){
				l=mid+1;
			}else{
				return mid;
			}
		}
		return l;
	}
	if(W==2000){
		a_=2002;
		for(int i=1;2002+i<=2200;i*=2){
			a[++a_]=2002;
			a[++a_]=2002+i;
		}
		for(int i=1;i<=15;++i){
			a[++a_]=2002;
			a[++a_]=2002+128;
		}
		sort(a+2003,a+a_+1);
		int l=1,r=2000;
		while(l<r){
			if(l==r-1){
				vector<int> v1,v2;
				v1.push_back(l);
				v2.push_back(r);
				int ttmp=compare_tastiness(v1,v2);
				if(ttmp==0){
					r=l;
				}else{
					l=r;
				}
				break;
			}
			int len=(r-l+1)/3;
			int mid1=l+len,mid2=l+len*2;
			if(mid2-mid1<r-mid2+1 && mid2<r){
				++mid2;
			}
			vector<int> v1,v2;
			v1.push_back(l);
			v1.push_back(r+1);
			v2.push_back(mid1);
			v2.push_back(mid2);
			int cha=l+r+1-(mid1+mid2);
			if(cha<0){
				cha=-cha;
				for(int i=2003,j=a_;i<j;++i,--j){
					if(cha>=a[j]-a[i]){
						cha-=a[j]-a[i];
						v1.push_back(j);
						v2.push_back(i);
					}
				}
			}else if(cha>0){
				for(int i=2003,j=a_;i<j;++i,--j){
					if(cha>=a[j]-a[i]){
						cha-=a[j]-a[i];
						v1.push_back(i);
						v2.push_back(j);
					}
				}
			}
			int ttmp=compare_tastiness(v1,v2);
			if(ttmp==1){
				r=mid1-1;
			}else if(ttmp==0){
				l=mid1;
				r=mid2-1;
			}else{
				l=mid2;
			}
		}
		return l;
	}
}
posted @ 2026-05-11 22:38  wlh__1  阅读(38)  评论(1)    收藏  举报