正题
题目链接:https://www.luogu.com.cn/problem/CF1672E
题目大意
有一个你不知道的长度为nnn的序列lll,你每次可以询问一个长度www。交互库会返回一个hhh表示最少能将lll分成多少连续段使得每一段[l,r][l,r][l,r]都满足∑i=lrli+r−l≤w\sum_{i=l}^rl_i+r-l\leq w∑i=lrli+r−l≤w。
但是,如果w<max{li}w<max\{l_i\}w<max{li}则会返回000。
你需要在n+30n+30n+30次询问内找到一组最小的w×hw\times hw×h。
1≤n≤20001\leq n\leq 20001≤n≤2000
解题思路
首先www的取值是可能有很多种的,但是hhh只有可能是1∼n1\sim n1∼n,一个暴力的想法是对于每个hhh都二分一个最小的www,但是这样显然搞不定这一题。
但是看这个三十我们肯定是要先二分一个的,考虑二分出h=1h=1h=1时的情况,那么我们就得到了∑i=1nli+n−1\sum_{i=1}^nl_i+n-1∑i=1nli+n−1,记为SSS。注意到hhh每一次增大最多帮我们剩下一个111,也就是询问hhh时的答案最少是∑i=1nli+n−h=S−h+1\sum_{i=1}^nl_i+n-h=S-h+1∑i=1nli+n−h=S−h+1。
所以对于一个hhh有用的www当且仅当h×w∈[S−h+1,S]h\times w\in[S-h+1,S]h×w∈[S−h+1,S],也就是w=⌊Sh⌋w=\lfloor\frac{S}{h}\rfloorw=⌊hS⌋时才有用。
所以对于每一个⌊Sh⌋\lfloor\frac{S}{h}\rfloor⌊hS⌋进行询问就好了。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,ans;
int main()
{scanf("%d",&n);int l=1,r=n*2000+n-1,h;while(l<=r){int mid=(l+r)>>1;printf("? %d\n",mid);fflush(stdout);scanf("%d",&h);if(h==1)r=mid-1;else l=mid+1;}ans=l;for(int i=2;i<=n;i++){printf("? %d\n",l/i);fflush(stdout);scanf("%d",&h);if(h)ans=min(ans,h*(l/i));}printf("! %d\n",ans);fflush(stdout);return 0;
}