HDU_4638_Group

Group

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2617    Accepted Submission(s): 1330


Problem Description
There are n men ,every man has an ID(1..n).their ID is unique. Whose ID is i and i-1 are friends, Whose ID is i and i+1 are friends. These n men stand in line. Now we select an interval of men to make some group. K men in a group can create K*K value. The value of an interval is sum of these value of groups. The people of same group's id must be continuous. Now we chose an interval of men and want to know there should be how many groups so the value of interval is max.
 

Input
First line is T indicate the case number.
For each case first line is n, m(1<=n ,m<=100000) indicate there are n men and m query.
Then a line have n number indicate the ID of men from left to right.
Next m line each line has two number L,R(1<=L<=R<=n),mean we want to know the answer of [L,R].
 

Output
For every query output a number indicate there should be how many group so that the sum of value is max.
 

Sample Input
  
  
1 5 2 3 1 2 5 4 1 5 2 4
 

Sample Output
  
  
1 2
 

Source

2013 Multi-University Training Contest 4


  • 可以说是相当玄学刺激的一道题
  • 看了题解弄了很久才明白
  • 要求是给出1到n的一个排列和m个区间的询问
  • 回答对于每个区间的连续段的个数
  • 这里连续段表示对于区间内的数,如果两个数相差1,那就归为一类,这样每段数都是连续的自然数
  • 对于这种题两种处理方法,一种是在线做法,一种是离线做法
  • 这道题在线做法行不通,我们考虑离线预处理,处理成一段一段的信息,可能把复杂度降到logn
  • 先考虑这样一个问题,对于一个固定区间,我们能不能在固定起点的前提下得到全部前缀的连续段个数
  • 首先,对于每个编号,都可以自己成为一段,但是因为有询问区间的存在才有两个以上单个段的联合
  • 我们先考虑n=3的情况,a[i]是原编号,b[i]是当前编号是否单独成为一段或是一个连续段的代表
  • 对于前缀的问题,我们自然的可以得出当一个编号出现而且它的左右相邻编号没有在它之前出现时,这个编号一定是单独成段的
  • 我们可以得到以下结果(第一行a[i],第二行b[i]):
  •  1  2  3
     1  0  0
    
     1  3  2
     1  1 -1
    
     2  1  3
     1  0  0
    
     2  3  1
     1  0  0
    
     3  1  2
     1  1 -1
    
     3  2  1
     1  0  0
  • 也就是说我希望通过上面的方式找到每一个连续段的开端,并且以所有前缀区间的答案就是前缀和
  • 并且我们可以知道对于一个编号,它的相邻编号和它自身的位置关系有3种
  • 一种是相邻编号在当前编号左边,对比上例,此时b[i]=-1;
  • 一种是相邻编号在当前编号两边,对比上例,此时b[i]=0;
  • 一种是相邻编号在当前编号右边,对比上例,此时b[i]=1;
  • 其实也不难理解
  • 如果相邻编号在右边,当前编号是当前前缀的一个单独点,单独成段,取1
  • 如果相邻编号在两边,当前编号总可以和前面一个相邻编号相容,取0
  • 如果相邻编号在左边,那么两个相邻编号一定不在同一个段内,对于当前的前缀,显然要让相邻编号融合,那么就要消掉其中一个段,取-1
  • 这个时候我们继续推,我们考虑如果预处理当前区间之后如果依次从左到右消去编号会怎么样
  • 我们发现如果消去区间最左边的编号数据,对应影响b数组的应该是消去编号的相邻编号,意思是两个相邻编号如果位置在剩余有效区间的话应该更新对应的b数组中的值
  • 修改后我们发现因为修改的两个编号b数组后两个编号本身的位置没有改变,所以其他编号不受影响
  • 对应下面的例子:
  •  1  2  3  5  4  9  6  8  7
     1  0  0  1 -1  1  0  0 -1
        1  0  1 -1  1  0  0 -1
           1  1 -1  1  0  0 -1
              1  0  1  0  0 -1
                 1  1  1  0 -1
                    1  1  0 -1
                       1  1 -1
                          1  0
                             1
  • 可以看到结果是对的
  • 那么我们就可以把这样的操作方式应用在本题
  • 数组a存储原编号,p存储编号位置,b存储编号连续段属性,c是树状数组计算前缀和
  • 用树状数组单点更新维护b数组即每个点状态的变更
  • 先对询问排序,自左向右消编号并进行单点更新维护单点状态
  • 计算前缀和即可

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cmath>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
typedef long long           LL ;
typedef unsigned long long ULL ;
const int    maxn = 1e5 + 50   ;
const int    inf  = 0x3f3f3f3f ;
const int    npos = -1         ;
const int    mod  = 1e9 + 7    ;
const int    mxx  = 100 + 5    ;
const double eps  = 1e-6       ;
const double PI   = acos(-1.0) ;

struct node{
    int id, u, v;
};
bool cmp(const node &l, const node &r){
    return l.u<r.u;
}
int lowbit(int x){
    return x&(-x);
}
node q[maxn];
int b[maxn], c[maxn];
void update(int n, int p, int val){
    while(p<=n){
        c[p]+=val;
        p+=lowbit(p);
    }
}
int cal(int n){
    int res=0;
    while(n>0){
        res+=c[n];
        n-=lowbit(n);
    }
    return res;
}
int ans[maxn], a[maxn], p[maxn];
int T, n, m, idx;
void reset(int le, int i, int ri){
    if(p[le]<i && p[ri]<i){
        update(n,i,-1-b[i]);
        b[i]=-1;
    }else if(i<p[le] && i<p[ri]){
        update(n,i,1-b[i]);
        b[i]=1;
    }else{
        update(n,i,0-b[i]);
        b[i]=0;
    }
}
int main(){
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    while(~scanf("%d",&T)){
        while(T--){
            scanf("%d %d",&n,&m);
            memset(p,0x3f,sizeof(p));
            memset(b,0,sizeof(b));
            memset(c,0,sizeof(c));
            for(int i=1;i<=n;i++){
                scanf("%d",&a[i]);
                p[a[i]]=i;
            }
            for(int i=1;i<=m;i++){
                scanf("%d %d",&q[i].u,&q[i].v);
                q[i].id=i;
            }
            sort(q+1,q+1+m,cmp);
            for(int i=1;i<=n;i++){
                int le=a[i]-1;
                int ri=a[i]+1;
                reset(le,i,ri);
            }
            idx=1;
            for(int i=1;i<=m;i++){
                while(idx<q[i].u){
                    update(n,idx,0-b[idx]);
                    b[idx]=0;
                    p[a[idx]]=inf;
                    int le=a[idx]-1;
                    if(idx<p[le] && p[le]<inf && le>0){
                        int nl=le-1;
                        int nr=le+1;
                        reset(nl,p[le],nr);
                    }
                    int ri=a[idx]+1;
                    if(idx<p[ri] && p[ri]<inf){
                        int nl=ri-1;
                        int nr=ri+1;
                        reset(nl,p[ri],nr);
                    }
                    idx++;
                }
                ans[q[i].id]=cal(q[i].v);
            }
            for(int i=1;i<=m;i++)
                printf("%d\n",ans[i]);
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值