牛客练习赛41 D.最小相似度(bfs/fwt)

探讨了在给定多个二进制串情况下,如何找到一个额外的二进制串,使得其与所有给定串的相似度(二进制串异或后0的个数)的最大值最小。介绍了两种解法,包括多源bfs和利用异或性质的快速沃尔什变换(FWT),并提供了详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

题目链接:https://ac.nowcoder.com/acm/contest/373/D

定义两个位数相等的二进制串 A,B 的相似度 SIM(A,B)= 二进制串A⊕B中0的个数

如 A=00010,B=01000,A⊕B=01010,所以 SIM(A,B)=3。

给定 N 个长度为 M 的二进制串S1,S2...SN。

现在的问题是找出一个额外的长度为 M 的二进制字符串 T ,

使得 max{SIM(S1,T),SIM(S2,T)...SIM(SN,T)} 最小。

因为满足条件的 T 可能不止一个,不需要输出串 T ,只需要输出这个最小值即可。

1<=N<=3e5,1<=M<=20

思路来源

https://www.cnblogs.com/butterflydew/p/10459737.html

https://www.cnblogs.com/LMCC1108/p/10470798.html

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40383289

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40376747

题解

解法1:

多源bfs,一共只有(1<<20)状态,每次只去更新到这个点的最短距离,

那么,我们记录的实际是二进制中A⊕B中1的个数的最小值,

不妨S1和V有3个1,S2和V有5个1,S3和V有7个1,m=20,dis[V]=3

那么S1和V有17个0,S2和V有15个0,S3和V有13个0,m-dis[V]=17

这个1的个数的最小值,被m减去之后,就是0的个数的最大值,

现在令0的个数的最大值这个数最小,也就是令17最小,那么令3最大即可

也就是寻找最大的dis[V],答案ans=m-dis[V]

 

想明白1和0的关系带来的min和max的转换之后,

寻找max的最小,就是寻找min的最大

正着做不好做,可以向对立做,思维的转化还是很重要呐……

 

解法2:

利用异或的性质A=B^C的情况下,C=A^B

A数组读入n个数,将读入的数的对应位置赋1,

如果最后的答案次数是x,数是T的话,

那么A内的所有数卷T所得的数,都会落在二进制位为0的个数<=x的数里面

我们只需二分次数x,B数组=[该数的二进制位为0的个数<=x],对应位置赋1

如果A卷B所得的新数组,在某一位置的值为n的话,说明该过程可逆

注意到,A数组的一个数只能对一个答案位置产生1的贡献,所以B数组元素虽然比逆过程多了,但是不影响

相当于,把A数组的每个数看成一个人,相当于第i个人取B区间上的一些点涂颜色i,然后问是否存在点有n种颜色

 

开始用int+取模,TLE

int不取模存在n==3e5的数据,a[i]*b[i]可能会爆,WA

最后开了ll不取mod,AC

代码1(bfs)

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
queue<int>q;
char s[25];
int dis[1<<20];
int n,m,v;
int tmp,nex,mx;
int to(char s[],int len)
{
    int ans=0;
    for(int i=0;i<len;++i)
    ans=ans*2+(s[i]-'0');
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<(1<<m);++i)
        dis[i]=INF;
    for(int i=0;i<n;++i)
    {
        scanf("%s",s);
        v=to(s,m);
        dis[v]=0;
        q.push(v);
    }
    while(!q.empty())
    {  
        tmp=q.front();q.pop();
        for(int i=0;i<m;++i)
        {
            nex=tmp^(1<<i);
            if(dis[nex]>dis[tmp]+1)
            {
                dis[nex]=dis[tmp]+1;
                q.push(nex);
            }
        }
    }
     
    for(int i=0;i<(1<<m);++i)
    mx=max(mx,dis[i]);
    printf("%d\n",m-mx);
    return 0;
}

代码2(fwt)

//inv2是MOD意义下2的逆元,inv2=(MOD+1)/2
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1<<20;
char s[25];
int n,m,N;
int l,r,v;
ll a[MAXN],b[MAXN];
int bit[MAXN];//dp统计每个值有多少位为1 
void FWT_xor(ll *a,int opt)
{
    for(int i=1;i<N;i<<=1)
        for(int p=i<<1,j=0;j<N;j+=p)
            for(int k=0;k<i;++k)
            {
                ll X=a[j+k],Y=a[i+j+k];
                a[j+k]=X+Y;a[i+j+k]=X-Y;
                if(opt==-1)a[j+k]=a[j+k]/2,a[i+j+k]=a[i+j+k]/2;
            }
}
bool ok(int x) 
{
	for(int i=0;i<N;++i)
	b[i]=((m-bit[i])<=x);//二进制中0的个数<=x 
	FWT_xor(b,1);
	for(int i=0;i<N;++i)
    b[i]=a[i]*b[i];
	FWT_xor(b,-1);
	for(int i=0;i<N;++i)
	if(b[i]==n)return 1;
	return 0;
}
int main()
{
	scanf("%d%d",&n,&m);
	N=1<<m; 
	for(int i=0;i<N;++i)//dp统计位数 
	bit[i]=bit[i>>1]+(i&1); 
	for(int i=0;i<n;++i)
	{
		scanf("%s",s);
		v=0;
		for(int j=0;j<m;++j)
		v=v*2+(s[j]-'0');
		a[v]++;
	}
	FWT_xor(a,1);
	l=0,r=m;
	while(l<r)
	{
		int mid=(l+r)/2;
		if(ok(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
	return 0; 
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值