题目大意
有一个n*m矩形,初始全零
有以下几种操作:
1、将一个位置取反(xor 1)
2、将一行取反
3、将一列取反
4、变成第k次操作后的状态
操作树
先来思考第四种操作,这看起来需要我们可持久化。
实际上,如果每个版本用一个节点表示,那么这形成了树的形状,每条树边代表一种1/2/3操作。
这种树就叫操作树。我们发现,本题同一个操作做两次等同于没做,因此可以很方便的撤销。
我们可以建出操作树后,对整颗树做一个遍历。每经过一条树边就做一遍对应的操作,就可以处理出每个版本的答案。
如何解决1/2/3?可以直接暴力。
或者思考一点常数优化,实际上单点修改和行修改都可以做到O(1),但是列修改还是O(n)的。
但如果列修改特别多呢?如果列修改数量多于行修改数量,我们可以尝试交换。这样常数减半。
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1000+10,maxq=100000+10,R=15000000;
char buf[R+7],*ptr=buf-1;
int a[maxn][maxn],fa[maxq],ans[maxq],rank[maxq];
int h[maxq],go[maxq],next[maxq];
int sum[maxn],bz[maxn];
int ask[maxq][3],sta[40];
int i,j,k,l,t,n,m,q,tot,top,cnt,num,now;
int read(){
int x=0,c=*++ptr;
while (c<48) c=*++ptr;
while (c>47) x=x*10+c-48,c=*++ptr;
return x;
}
void write(int x){
if (!x){
putchar('0');
putchar('\n');
return;
}
top=0;
while (x){
sta[++top]=x%10;
x/=10;
}
while (top){
putchar(sta[top]+'0');
top--;
}
putchar('\n');
}
void add(int x,int y){
go[++top]=y;
next[top]=h[x];
h[x]=top;
}
void work(int id){
int i,j,t=ask[id][0];
if (t==1){
i=ask[id][1];j=ask[id][2];
if (bz[i]!=a[i][j]){
sum[i]--;
num--;
}
else{
sum[i]++;
num++;
}
a[i][j]^=1;
}
else if (t==2){
i=ask[id][1];
num+=m-sum[i]*2;
sum[i]=m-sum[i];
bz[i]^=1;
}
else{
j=ask[id][1];
fo(i,1,n){
if (bz[i]!=a[i][j]){
num--;
sum[i]--;
}
else{
num++;
sum[i]++;
}
a[i][j]^=1;
}
}
}
void solve(int x){
ans[x]=num;
int t=h[x];
while (t){
work(go[t]);
solve(go[t]);
work(go[t]);
t=next[t];
}
}
int main(){
freopen("present.in","r",stdin);freopen("present.out","w",stdout);
fread(buf,1,R,stdin);
n=read();m=read();q=read();
rank[0]=0;
fo(l,1,q){
t=read();
if (t==4){
k=read();
now=rank[k];
}
else if (t==1){
ask[++tot][0]=t;
ask[tot][1]=read();ask[tot][2]=read();
add(now,tot);
now=tot;
}
else{
ask[++tot][0]=t;
ask[tot][1]=read();
add(now,tot);
now=tot;
}
rank[l]=now;
}
solve(0);
fo(i,1,q) write(ans[rank[i]]);
}