抽象一下,此题给你10W个数,让你求一个[i,j],使得a[i]到a[j]的数的二进制每一位上的1出现的次数相等,并使j-i+1最大。
解法:
我们把每个数化为二进制把每一位对应地保存在一个长为k的数组l,l[i][j]的值就表示第i个数的第j位是1还是0。
令p[i][j]表示把前i项的第j位累加起来的值,即p[i][j]=l[i][j]+l[i-1][j]+...+l[1][j];
最关键的一步来了,把这n个数p[i][j]都减去p[i][1],那么只需要寻找一对x,y,使得p[x][i]==p[y][i], 1<=i<=k,记录最大的y-x为答案。
为什么?
因为如果[x,y]上二进制每一位上的1出现的次数相等,那么
对于任意的a,b,有p[x][a]-p[y][a] =p[x][b]-p[y][b] (1<=a<=k,1<=b<=k)
=> p[x][a]-p[x][b]=p[y][a]-p[y][b]
我令b=1,p[x][i]把每一位上的都减去p[x][1]后,必然p[x][i]=p[y][i] (1<=i<=k)
所以我只需找到一个最大的y-x即可。
那么此时,问题便转化为了,有个二维数组p,找到一对x,y,使得p[x][i]==p[y][i], 1<=i<=k,求max(y-x)。
显然O(n^2)会超时,于是就用到了hash。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#define MAXHASH 100001
using namespace std;
int n,len,l[100020][35],ans,a[100020],fk[100020][35];
vector<int> v[100020];
int jdz(int n)
{
return n>0?n:-n;
}
int f(int c)
{
int ret =0;
for (int i =1; i <=len; i++)
ret+=fk[c][i]*i;
return jdz(ret);
}
bool judge(int a,int b)
{
int i;
for (i=1; i<=len; i++)
{
if (fk[a][i] != fk[b][i])
break;
}
if (i == len+1)
return true;
else
return false;
}
int main()
{
int i,j,t;
scanf("%d%d",&n,&len);
for (i=1; i<=n; i++)
{
scanf("%d",a+i);
// printf("\n");
}
memset(l,0,sizeof(l));
for(i=1;i<=n;i++)
{
t=a[i];
for(j=1;j<=len;j++)
{
l[i][j]=l[i-1][j]+t%2;
t=t/2;
}
}
for(i=1;i<=n;i++)
for(j=1;j<=len;j++)
fk[i][j]=l[i][j]-l[i][1];
for (i=0; i<100020; i++)
v[i].clear();
ans=0;
for (i=0; i<=n; i++)
{
t=f(i);
t%=MAXHASH;
for (j=0; j<v[t].size(); j++)
{
if (judge(v[t][j],i))
{
ans=ans<jdz(i-v[t][j])?jdz(i-v[t][j]):ans;
break;
}
}
if (j == v[t].size())
v[t].push_back(i);
}
printf("%d\n",ans);
}