题意:给你m代表有2^m 个不同的01串,现在去掉n个,问剩下的2^m-n个串中 排序后中位的字符串. 中位计算:(id-1)/2
做法:因为m只有60,考虑字符串当作longlong的整数来算。最初的中位数一定是01111形式就是2^(m-1)-1.
然后n只有100,考虑对初始的中位数上下枚举100个数,然后判断小于当前数和大于当前数是否相等。
至于怎么 去掉 n 的 影响。对当前数在n中二分查找大于当前数x的个数。那么大于x的个数就少了这么多。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define maxn 1005
#define inf 1e9
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
inline ll read()
{
ll x=0,w=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') w=-1; c=getchar();}
while(c<='9'&&c>='0') {x=(x<<1)+(x<<3)+c-'0'; c=getchar();}
return w==1?x:-x;
}
const int N=1e2+10;
char s[N];
int n,m;
ll X[N],len,now,L,R;
ll cal(char s[])
{
ll now=1,ans=0;
for(int i=m;i>=1;--i){
if(s[i]=='1') ans+=now;
now*=2;
}
return ans;
}
map<ll,int>mp;
void print(ll x)
{
stack<int>sta;
while(x) {
sta.push(x%2);
x=x/2;
}
int len=sta.size();
rep(i,1,m-len) sta.push(0);
while(sta.size()){
printf("%d",sta.top());
sta.pop();
}
}
int valid(ll x)
{
//printf("x:%lld\n",x);
ll mx=R-x,mi=x+1;
int id=lower_bound(X+1,X+1+len,x)-X;
int t1=len-id+1;
mi-=(id-1);
mx-=t1;
//printf("x:%lld mi:%lld mx:%lld t1:%d\n",x,mi,mx,t1);
if(mx==mi||mx+1==mi) return 1;
return 0;
}
int main()
{
int _=read();
while(_--)
{
n=read(),m=read();
mp.clear();
now=(1ll<<(m-1))-1;
L=0,R=(1ll<<m)-1;
len=0;
rep(i,1,n){
scanf("%s",s+1);
X[++len]=cal(s);
mp[X[len]]=1;
}
sort(X+1,X+1+len);
ll ans=now;
//printf("ansans:%lld now:%lld\n",ans,now);
int flag=1;
for(int i=1;i<=100&&ans>=0&&flag;++i){
if(mp[ans]==0&&valid(ans)){
print(ans);
flag=0;
break;
}
ans--;
}
ans=now;
for(int i=1;i<=100&&ans<=R&&flag;++i){
if(mp[ans]==0&&valid(ans)){
print(ans);
flag=0;
break;
}
ans++;
}
puts("");
}
}
/*
1
7 3
000
001
010
100
101
110
111
*/
设最终答案为med,那么要求出答案只需要知道给出的n个数中,有几个在med右侧(>med),有几个在med左侧(<med)即可。
此处有两个思维上的难点:
当我们依次将ai与med比较时,med是在动态变化的(意味着当前关于ai和med大小的判断可能有误)
删去一个数后,数列长度发生改变,其余点的中位数求起来也比较麻烦。
所以为了解决以上两个问题,可以简单构造一下。
如图,一开始,我们假设给出的n个点都在med左侧,因为是连续的,所以这个数列的性质非常清晰。设数列左端l=n,右端r=2^m-1,那么实际数列长度为(r-l+1),中位数就是(l+r)/2
但是当然ai也可能在med右侧,此时如何调整呢。之前说到,我们只关注这个ai在med左侧还是右侧,而不关注它具体是多少。所以,只要把任意两个med左侧和右侧的数互换一下就可以了。为了维护数列的连续性,我们可以交换n-1和2^m-1这两个数。
很显然,我们只是把蓝色的一段整体左移了一个数而已,那么med也只需要减1就可以完成变换了。
最后一个问题是,med仍然是动态变化的,如何在判断每个ai“在med左侧还是右侧”的时候都能确保判断是正确的呢。
只要先将ai从大到小排序即可。(具体说是因为这种构造方式维护了med的单调递减,那么排序后如果ai>=med,ai一定大于等于最终的中位数,如果ai<med,后续比较中ai始终小于med,med不再发生变化,最终的中位数就是med)
所以这种构造法最关键的地方是维护了区间长度和连续性,对于一个固定长度并连续的区间,处理起来就非常方便。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100+5;
char s[N],ans[N];
ll l,r,jc[N],A[N];
bool cmp(ll a,ll b){ return a>b; }
int main()
{
int T,n,m;
jc[0]=1;
for (int i=1;i<=60;i++) jc[i]=jc[i-1]*2;
cin>>T;
while(T--)
{
cin>>n>>m;
l=0,r=jc[m]-1;
for (int i=1;i<=n;i++)
{
scanf("%s",s);
ll tmp=0;
for (int j=0;j<m;j++)
{
tmp=tmp*2+s[j]-'0';
}
A[i]=tmp;
}
sort(A+1,A+1+n,cmp);
ll cur=(n+r)/2;
for (int i=1;i<=n;i++)
{
if (A[i]>=cur)
cur--;
}
ans[m]='\0';
for (int i=m-1;i>=0;i--)
{
ans[i]=cur%2+'0';
cur/=2;
}
printf("%s\n",ans);
}
return 0;
}
下面是用二进制bitset的代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
void solve(){
int n,m;
cin>>n>>m;
ll a[n];
string s;
ll ans=((1LL<<m)-n-1)/2;
for(int i=0;i<n;i++){
cin>>s;
a[i]=bitset<64>(s).to_ullong();
}
sort(a,a+n);
for(auto i:a) ans+=(i<=ans);
cout<<bitset<64>(ans).to_string().substr(64-m)<<endl;
}
int main(){
int t;
cin>>t;
while(t--) solve();
}