Codeforces Round #644 (Div. 3) H Binary Median

5 篇文章 0 订阅
1 篇文章 0 订阅

题意:给你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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值