分块-bzoj2821: 作诗(Poetize)

http://hzwer.com/3663.html
我的程序好垃圾,常数好像很大的样子,本来是WA,然后改好之后交上去超时;
改来改去半天还是TLE,后来把分块长度变成sqrt(n/log2(n)),就过了;
k=sqrt(n/log(n))/log(2)
然而我自己并不会求这个;
唉数学差;
黄学长讲的很详细了;
因为是强制在线,我们先尽可能多的先预处理;
就是那个f[i][j];
因为我们的预处理,所以对于一个区间,我们需要暴力求解的数字最多不过两个块,区间里成块的都算好了放在f数组里面了;
对于不在整块里的数字,我们按黄学长说的就好啦;
但是统计次数的时候,我们用前坠和就炸;
所以我们要用前向星,排序一遍,把相同的数字按原始下标有序地排到一起;这样我们就可以通过两次二分来求出某数字在某区间出现的次数了;
代码思路是很清晰的,应该也好理解,就是跑得有点慢…..

#include<cstdio>//cfb
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
struct kuai{//就是分块 
    int ll,rr;//这个是区间范围 
}c[1000];
struct cs{//a数组有两个用途,x,num是用来排序的;ll,rr,代表排序后数字i出现的范围 
    int x,num,ll,rr;
}a[100001];
int f[1000][1000],b[100001],aa[100001];//b是一个计数器,aa就是存放读入的数组; 
int n,m,ll,x,y,ans,z,q;
inline int read()//黄学长的 
{
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
bool cmp(cs x,cs y){if(x.x!=y.x)return x.x<y.x;return x.num<y.num;}
void make(){
    int l=1,k=sqrt(n/log(n))/log(2);
    while(l<=n){//分块; 
        c[++ll].ll=l;
        c[ll].rr=min(l+k-1,n);
        l+=k;
    }
    for(int i=1;i<=ll;i++){
        int sum=0;
        for(int j=i;j<=ll;j++){
            for(int k=c[j].ll;k<=c[j].rr;k++){
                b[a[k].x]++;
                if(b[a[k].x]>1&&(b[a[k].x]&1))sum--;
                if(!(b[a[k].x]&1))sum++;
            }
            f[i][j]=sum;
        }
        for(int j=c[i].ll;j<=c[ll].rr;j++)b[a[j].x]=0;//比memset快好多; 
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++){if(!a[a[i].x].ll)a[a[i].x].ll=i;a[a[i].x].rr=i;}
}
int max_min(int x,int y,int z){//在x,y区间里找到比z大的最小的数,用于区间左端; 
    int ans=y;
    while(y>=x){
        int mid=(y+x)>>1;
        if(a[mid].num>=z){
            ans=min(ans,mid);
            y=mid-1;
        }else x=mid+1;
    }
    return ans;
}
int min_max(int x,int y,int z){//在x,y区间里找到比z小的最大的数,用于区间左端; 
    int ans=x;
    while(y>=x){
        int mid=(y+x)>>1;
        if(a[mid].num<=z){
            ans=max(ans,mid);
            x=mid+1;
        }else y=mid-1;
    }
    return ans;
}
void check(int num,int x,int y,int l,int r){
    x=max_min(a[num].ll,a[num].rr,x);
    y=min_max(a[num].ll,a[num].rr,y);
    l=max_min(a[num].ll,a[num].rr,l);
    r=min_max(a[num].ll,a[num].rr,r);
    x=y-x+1; y=r-l+1;//通过二分出来的下标来统计区间内数字出现次数 
    if(!y&&x&&!(x&1))ans++;//我一开始漏掉了Y=0的情况 
    if(y&&(y&1)&&!(x&1))ans++;
    if(y&&!(y&1)&&(x&1))ans--;
}
void outit(int x,int y){
    int first=0,last=0;
    for(int i=1;i<=ll;i++)if(x<=c[i].ll&&c[i].rr<=y){if(!first)first=i;last=i;}
    ans=f[first][last];//一大块的答案树算好了的 
    if(last){
        for(int i=x;i<=c[first].ll-1;i++)if(!b[aa[i]]){check(aa[i],x,y,c[first].ll,c[last].rr),b[aa[i]]=1;}
        for(int i=y;i>=c[last ].rr+1;i--)if(!b[aa[i]]){check(aa[i],x,y,c[first].ll,c[last].rr),b[aa[i]]=1;}
        for(int i=x;i<=c[first].ll-1;i++)b[aa[i]]=0;
        for(int i=y;i>=c[last ].rr+1;i--)b[aa[i]]=0;
    }else{//找不到完整块的处理 
        for(int i=x;i<=y;i++)
        if(!b[aa[i]]){
            b[aa[i]]=1;
            int l=max_min(a[aa[i]].ll,a[aa[i]].rr,x);
            int r=min_max(a[aa[i]].ll,a[aa[i]].rr,y);
            if(!((r-l+1)&1))ans++;
        }
        for(int i=x;i<=y;i++)b[aa[i]]=0;
    }
    printf("%d\n",ans);
}
int main()
{
    n=read();q=read();m=read();
    for(int i=1;i<=n;i++)a[i].x=read(),a[i].num=i,aa[i]=a[i].x;
    make();
    while(m--){
        x=read(),y=read();
        outit(min((x+ans)%n+1,(y+ans)%n+1),max((x+ans)%n+1,(y+ans)%n+1));//强制在线 
    }
}
posted @ 2017-02-22 18:28  largecube233  阅读(128)  评论(0编辑  收藏  举报