传送门:Medians Strike Back
标签:构造
题目大意
规定一个长度为n的序列的中位数为:1、n为奇数时为序列排序后第(n+1)/2位;2、n为偶数时为第n/2和n/2+1位中出现次数较多的那个,若出现次数相同则为较小的那个。再规定一个序列的shikness为该序列中位数的出现次数,一个序列的nitness为该序列所有子段的shikness的最大值。现在给出一个正整数n,需要你构造一个序列a1,a2,······,an,满足1<=ai<=3且该序列的nitness最小。
输入:T组数据,每行一个正整数n(1<=n<=1e9),代表序列长度。
输出:一个正整数,代表nitness的最小值。
算法分析
- 看n的数据范围就知道这题要么O(1)要么O(logn),先用高复杂度的O(3n)算法打表看看有无规律,实践后发现没有普遍公式,那么我们就想O(logn)的做法。带log的算法有很多,但考虑到空间复杂度的限制和数据范围,这题应该是用二分。那我们距离通过本题就差临门一脚了,只要知道二分用在哪里即可。
- 先思考shikness最小的情况。排序后1在2前面,2在3前面,这是必然的,如果让1或者3作为中位数,那么shikness的值至少为n/2,这样显然不是最优的。我们如果让2在序列的最中间,即n为奇数时a(n+1)/2=2,n为偶数时an/2=an/2+1=2,然后让2前面全是1,2后面全是3,就能将序列的shikness控制在区间[1,2]之间,毫无疑问没有更优的策略。
- 想让shikness最小很简单,但nitness的情况就复杂很多了。它涉及到每个子段的shikness,那我们的思路不变,还是尽量保证2在最中间,你可能会想到123无限循环的构造,这样一来答案虽然稳定,但会变成n/3。我们不需要保证任意一个子段都是最优的情况,只要保证其shikness小于整个序列的shikness就行了。
- 我们假设这是一场博弈,对手要从我们构造的序列中找到最劣的一段。我们上面已经分析过,最优构造下1和3的数量相差不超过一,且n为奇时需要一个2,n为偶时需要两个2。如果完整的序列满足条件,那么对手肯定会选择一个不含2的最长子段或者只含一个2的最长偶数段。为了让最劣子段尽量满足条件,我们先保证其1和3的数量尽可能接近,即构造出13131313循环的序列。然后我们再考虑把一些2插进去,假设我们每隔k个数插一个2,那么对手肯定会选择一个长度为2k的只包含一个2的子段,使得shikness为k。
- 对于一个13 2 13 2 13的序列,我们发现两个2将原序列分成了三段,中间那段对左右两段做的贡献都为k,也就是2k。我们如果要优化,就会把中间的部分向两边分散,使其贡献减小。那么最优情况就变成了原本相差k的两个2最后相邻了。这样一来就得到了正解:每两个2捆在一起作为一个节点插入13序列中。假设每两个节点间相差k个数,那么子段shikness最大为k/2。因为要保证整个序列的nitness等于shikness,也就是2的总个数,所以我们只要让k/2小于等于2的数量即可。根据这一点对2的个数进行二分,就能实现O(logn)的做法。
代码实现
#include <iostream>
using namespace std;
#include <cmath>
#include <algorithm>
#include <map>
#define lowbit(x) x&-x
int n,ans,a[1001],c[1001],cnt[5];
int mi=1e9;
int main(){
long long i,j,a,b,z,l,r,x,y,mid,k,m,T,cnt,len;
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n;
l=1;r=n;
while(l<r){
mid=(l+r)>>1;
x=n-mid;
k=mid/2;
y=x/(k+1);
z=x%(k+1);
if(z)y++;
y=(y+1)/2;
if(y>mid)l=mid+1;
else r=mid;
}
cout<<l<<'\n';
}
return 0;
}