您的位置:首页 > 其它

分块-bzoj2821: 作诗(Poetize)

2017-02-22 18:28 253 查看
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));//强制在线
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: