有一个n*m的矩阵,一开始位置(x,y)上的元素为(x-1)*m+y。接下来有q个操作:每次操作为(x,y),表示先输出位置(x,y)上的值,设t为(x,y)上的值,然后把第x行[y+1…m]上的每个数往前移一位,把最后一列[x+1…n]上的每个数往前移一位,最后把t放到位置(n,m)。
n,m,q<=300000
考场上大概想到了做法,但由于一个地方把n和m打反而少了35分,同时有两个点被ccf的老爷机卡常。
大概就是说注意到每次操作只会修改某一行和最后一列,我们就对每一行和最后一列分别开一棵线段树。每次操作的时候就对第x行和最后一列的线段树各种操作一下。由于每一行和最后一列最多会被插入q个数,所以每棵线段树的大小要开大q。
这样显然会炸空间。但注意到一开始每行线段树的前m个位置是满的且元素是公差为1的等差数列,我们便可以先不开这部分的节点,等要用到的时候再把那些节点加上去。这样就可以保证空间了。
超级好打!
时间复杂度O(nlogn),空间复杂度O(nlogn)。
据说正解是树状数组但并不是太会。。。
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int N=300005;
int n,m,q,sz,num[N],rt[N],tag,z;
struct tree{int s,l,r;LL id;}t[N*50];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
LL kth(int &d,int l,int r,int k)
{
if (!d) d=++sz,t[d].s=max(0,min(r,z)-l+1);
t[d].s--;
if (l==r)
{
if (l<=z) {tag=1;return l;}
else return t[d].id;
}
int mid=(l+r)/2,ls=(!t[d].l?max(0,min(mid,z)-l+1):t[t[d].l].s);
if (ls>=k) return kth(t[d].l,l,mid,k);
else return kth(t[d].r,mid+1,r,k-ls);
}
void ins(int &d,int l,int r,int x,LL y)
{
if (!d) d=++sz,t[d].s=max(0,min(r,z)-l+1);
t[d].s++;
if (l==r) {t[d].id=y;return;}
int mid=(l+r)/2;
if (x<=mid) ins(t[d].l,l,mid,x,y);
else ins(t[d].r,mid+1,r,x,y);
}
int main()
{
n=read();m=read();q=read();
for (int i=1;i<=n;i++) num[i]=m-1,rt[i]=++sz,t[sz].s=m-1;
num[0]=n;rt[0]=++sz;t[sz].s=n;
for (int i=1;i<=q;i++)
{
int x=read(),y=read();
if (y0;z=m-1;
LL id1=kth(rt[x],1,m+q,y);
if (tag) id1=(LL)(x-1)*m+id1;
printf("%lld\n",id1);
tag=0;z=n;
LL id2=kth(rt[0],1,n+q,x);
if (tag) id2=(LL)id2*m;
num[0]++;z=n;ins(rt[0],1,n+q,num[0],id1);
num[x]++;z=m-1;ins(rt[x],1,m+q,num[x],id2);
}
else
{
tag=0;z=n;
LL id1=kth(rt[0],1,n+q,x);
if (tag) id1=(LL)id1*m;
printf("%lld\n",id1);
num[0]++;z=n;ins(rt[0],1,n+q,num[0],id1);
}
}
return 0;
}