poj 3274 Gold Balanced Lineup(HASH)

大致题意: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 }

转载于:https://www.cnblogs.com/mcflurry/archive/2012/06/19/2554490.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值