列队----树状数组
有趣等级:一般般(试问在理解了好久,写了好久,调了好久以后我还会觉得它有趣吗?!)
有趣理由:vector与树状数组配合极其默契
注意到有%30的数据x=1
这意味着所有的操作只涉及第一行以及最后一列
当第一行有人出队时,最后一列会补上,出队的人又会补上最后一列
考虑将第一行的人和最后一列的人压成一个序列,每次将第一行出队的人排到序列的最后,再将序列出队的人后面的人都往前挪,这也能完成状态的模拟,但是这样做时间复杂度不可接受。
复杂度的瓶颈在于将人往前挪,于是我们考虑优化这个东西。
初始给每一个位置都标为1,表示这个位置上有1个人。当一个人离开后,我们不让后面的人上来覆盖这个位置,而是将这个位置打上标记0,表示这个位置上没有人。当要第一行第y列的人出队时,考虑这个人原来是在哪一个位置上的。假设这个人原来在位置h上,现在它在第y列,这意味着它以及它前面有y个人,所以我们只要找到一个最小的位置使得这个位置以及这个位置之前有y个人,就可以得出h,然后计算出这个人的编号,将它位置标记为0并将它加到序列末尾。
由于0和1的标记,我们要找的其实就是最小的前缀和为y的位置。用树状数组来动态维护前缀和,又由于前缀和单调不递减,故可以用二分来查找这个位置。
当然原来的位置h可能大于m,这说明它是由第一行出队的人加到最后一列末尾的数,这部分可以用vector/数组维护
#include <bits/stdc++.h>
using namespace std;
int n,m,q,c[1000010];
long long b[1000010];
int N;
void Add(int x,int v)
{
for (;x<=N;x+=x&(-x))
c[x]+=v;
}
int Get(int x)
{
int ans=0;
for (;x;x-=x&(-x))
ans+=c[x];
return ans;
}
int main()
{
N=n+m+q;
for (int i=1;i<=m;i++)
b[i]=i,Add(i,1);
for (long long i=2;i<=n;i++)
b[m+i-1]=i*m,Add(m+i-1,1);
int nu=n+m-1;
for (int i=1;i<=q;i++)
{
int x,y;
int id=0;
scanf("%d%d",&x,&y);
int l=1,r=nu;
while (l+1<r)
{
int mid=(l+r)>>1;
if (Get(mid)>=y) r=mid;else l=mid+1;
}
if (Get(l)==y) id=l;else id=r;
b[++nu]=b[id];
printf("%lld\n",b[id]);
Add(id,-1);
Add(nu,1);
b[id]=0;
}
}
%100数据
两行之间除最后一列外是不相互影响的
不妨将每一行看做只有1~m-1列,最后一列特殊处理
然后对于每一行的询问都按时间顺序单独预处理出这个人原来在这一行的位置h(注:这里只要求h,不要求h具体编号)
这可以排序,然后像上面%30的数据一样做到
再将询问按照时间排序,依次操作
这里对于每一行和最后一列分别维护一个vector,用来存储增进来的数
对于每个询问(x,y):
找出最后一列第x个数,并计算出它原来在最后一列的位置hh(第hh行)
如果hh<=n这个数的编号就是hh*m,否则就时v[0] [hh-n-1] (v[0]是列的vector,下标从0开始)
-
y<m:
-
h<m,直接计算编号。
-
h>=m,v[x] [h-m] (下标从0开始)
-
然后将这个人(实际是编号)加入到最后1列的vector中,将hh加入到该 行的vector中。
-
-
y==m:
- 编号就是hh
- 将hh加入到最后1列的vector中
以上是大概操作,看似也解决了问题,但貌似还遗留了一个问题
如何求出最后一列的x个数??
注意到对于每一行(列1~m-1)询问第y个数(第y列)因为对于y的预处理我们得到了h,所以每次询问的h对于每一行来说是不会重复的,直接用就好了。
但是对于最后一列询问第x个数(第x行),这我们没有进行过预处理,x是会重复的,所以我们也要像%30的数据那样子维护出hh,所以一边做一边维护就好了
#include <bits/stdc++.h>
using namespace std;
int n,m,q,c[600010],INF=600000;
vector <long long> vec[300010];
struct dsa
{
int x,y,id,gg;
}a[300010];
bool cmp(dsa p1,dsa p2)
{
if (p1.x==p2.x) return p1.gg<p2.gg;
return p1.x<p2.x;
}
bool cmp_(dsa p1,dsa p2)
{
return p1.gg<p2.gg;
}
void Add(int x,int v)
{
for (;x<=INF;x+=x&(-x))
c[x]+=v;
}
int Find(int x)
{
int ans=0;
for (;x;x-=x&(-x))
ans+=c[x];
return ans;
}
int main()
{
freopen("phalanx.in","r",stdin);
freopen("phalanx.out","w",stdout);
scanf("%d%d%d",&n,&m,&q);
for (int i=1;i<=q;i++)
scanf("%d%d",&a[i].x,&a[i].y),a[i].gg=i;
sort(a+1,a+q+1,cmp);
int L=1;
for (int i=1;i<m;i++)
Add(i,1);
for (int i=1;i<=n;i++)
{
int nu=m-1;
if (L>q) break;
if (a[L].x!=i) continue;
int R=L;
while (a[R+1].x==i) R++;
for (int j=L;j<=R;j++)
{
if (a[j].y==m) continue;
int l=1,r=nu;
while (l+1<r)
{
int mid=(l+r)>>1;
if (Find(mid)>=a[j].y) r=mid;
else l=mid+1;
}
if (Find(l)==a[j].y) a[j].id=l;else a[j].id=r;
Add(a[j].id,-1);
Add(++nu,1);
}
for (int j=L;j<=R;j++)
{
if (a[j].y==m) continue;
Add(a[j].id,1);
Add(nu--,-1);
}
L=R+1;
}
sort(a+1,a+q+1,cmp_);
for (int i=1;i<m;i++)
Add(i,-1);
for (int i=1;i<=n;i++)
Add(i,1);
int N=n;
for (int i=1;i<=q;i++)
{
int l=1,r=N;
long long num=0;
while (l+1<r)
{
int mid=(l+r)>>1;
if (Find(mid)>=a[i].x) r=mid;
else l=mid+1;
}
int h=0;
if (Find(l)==a[i].x) h=l;else h=r;
if (a[i].y<m)
{
if (a[i].id>=m)
{
num=vec[a[i].x][a[i].id-m];
vec[a[i].x][a[i].id-m]=0;
}
else num=(a[i].x-1)*1LL*m+a[i].id;
if (h<=n) vec[a[i].x].push_back(h*1LL*m);
else vec[a[i].x].push_back(vec[0][h-n-1]),vec[0][h-n-1]=0;
Add(h,-1);
Add(++N,1);
vec[0].push_back(num);
}
else if (a[i].y==m)
{
if (h>n) num=vec[0][h-n-1],vec[0][h-n-1]=0;
else num=h*1LL*m;
Add(h,-1);
vec[0].push_back(num);
Add(++N,1);
}
printf("%lld\n",num);
}
return 0;
}
然后发现树状数组并不比动态开点线段树好搞,luogu上有些代码跑的飞快,不知有什么妙招,树状数组还是稳的,时限2s,树状数组跑不到1s。
我为什么要写树状数组呢,因为我考试的时候想到了%30x=1的做法,就想着,不如续下去吧
事实证明还没续完我就要续命了。。。
为什么树状数组跑的比动态开点线段树满啊