大致题意:Farmer John有N头牛,N最多是10^5 ,每头牛最多有30种特征,一头牛所具有的特征可以用一个数来表示,将这个数化成2进制,第i位上权为1,说明它具有第i种特征,如果有一个区间(牛编号连续),使得这个区间的牛的每种特征之和相等,则这个区间为平衡区间。现在告诉你牛的个数n,特征个数k和每头牛的特征值,让你求最大的平衡区间。
Time Limit : 4000/2000ms (Java/Other) Memory Limit : 131072/65536K (Java/Other)
样例:
Sample Input
7 3 7 6 7 2 1 4 2
Sample Output
4
选择的牛是第3头到第6头
思路:不妨先统计出前i头牛所有的特征值,则第i头牛到第j头牛的特征值就很容易求了。
num[i][k]代表前i头牛共有num[i][k]个牛具有第k个特征,如果区间[i,j]满足平衡区间,那么有:
num[j][0]-num[i][0]=num[j][1]-num[j][1]=...=num[j][k-1]-num[i][k-1]
想到这里,这个题目应该就是检索了,但是N是10^5,枚举区间时间复杂度是O(k*n^2),10^10量级在2S内是跑不完的,只能优化,尽量缩小检索区间,这里方法有很多,可以根据num基数排序,也可以用HASH。我觉得HASH比较好想:
上方推导的关系式不是很易于检索,也不好确定键值,于是我们对上面的等式稍稍变形:
num[j][0]-num[i][0]=num[j][1]-num[j][1],我们可以得到num[j][0]-num[j][1]=num[i][0]-num[i][1],那么:
num[j][0]-num[j][1]=num[i][0]-num[i][1],num[j][0]-num[j][2]=num[i][0]-num[i][2]...num[j][0]-num[j][k-1]=num[i][0]-num[i][k-1]
这样我们可以预处理每一个num序列,设sub[i][j]=num[i][0]-num[j],则问题就转化为求最大的区间[i,j],使得sub[i]和sub[j]这两个数组的第1~k-1个元素相等。
直接寻找的时间复杂度仍是O(k*n^2),但我们比较容易对sub数组进行HASH。HASH的方法是将sub[i]十进制转化,超出数组范围就取余,链表存储具有相同键值的元素,然后对N个数据直接在相应的链表中查询,更新最大值即可。假设每组链中的元素远小于N,并且忽略k(k比较小),那么我们就能够在小于O(n^2)的时间内检索出结果。
还算是比较简单的数据结构题吧。
这题有USACO官方题解和数据,数据链接:
http://ace.delos.com/TESTDATA/MAR07_4.htm
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 using namespace std; 5 const int MAXNUM=100003; 6 struct stru{ 7 int num[31],dx; 8 stru *next; 9 }; 10 stru str[MAXNUM+5]; 11 int num[MAXNUM][31],n,k; 12 bool is(int n1[],int n2[]){ 13 for(int i=0;i<k-1;i++){ 14 if(n1[i]!=n2[i+1])return false; 15 } 16 return true; 17 } 18 int main(){ 19 int i,j; 20 while(~scanf("%d%d",&n,&k)){ 21 if(k==1){printf("%d\n",n);continue;} //不知道有没有k=1的数据,也不确信算法是否能够解决k=1时的最长区间,不过答案显而易见,索性直接输出 22 memset(num,0,sizeof(num)); 23 memset(str,0,sizeof(str)); 24 for(i=1;i<=n;i++){ 25 int temp; 26 scanf("%d",&temp); 27 for(j=0;j<k;j++){ 28 num[i][j]=num[i-1][j]+temp%2; 29 temp>>=1; 30 } 31 } 32 for(i=0;i<=n;i++){ //这里不要忘了把第一组全0序列也加进去 33 for(j=1;j<k;j++)num[i][j]-=num[i][0]; 34 int temp=0; 35 for(j=1;j<k;j++){ //HASH 36 temp=(temp+num[i][j]); 37 temp%=MAXNUM; 38 if(j!=k-1)temp*=10; 39 } 40 temp=(temp%MAXNUM+MAXNUM)%MAXNUM; 41 num[i][0]=temp; 42 stru *s=new stru; 43 s->next=NULL;s->dx=i; //一定要将s->next设成空,很多题因此RE过很多次 44 for(j=1;j<k;j++)s->num[j-1]=num[i][j]; 45 s->next=str[temp].next; 46 str[temp].next=s; 47 } 48 int ans=-(int)1e10; 49 for(i=1;i<=n;i++){ //寻找最大区间 50 stru *temp=&str[num[i][0]]; 51 while(temp->next!=NULL){ 52 if(is(temp->next->num,num[i])){ 53 ans=ans>i-temp->next->dx?ans:i-temp->next->dx; 54 } 55 temp=temp->next; 56 } 57 } 58 printf("%d\n",ans); 59 } 60 return 0; 61 }