最初市松模様を読み落としてました。
https://atcoder.jp/contests/yahoo-procon2019-final-open/tasks/yahoo_procon2019_final_c
問題
無限に大きなグリッドがある。
初期状態で各マスは白く塗られている。
うち矩形領域がN回与えられるので、矩形領域のうち市松模様上に位置するマスについて、白黒反転させる。
最終的に黒マスはいくつあるか。
解法
まず市松模様が厄介なのでこれをどうにかしよう。
グリッドを、列番号の偶奇、および行番号の偶奇で計4通りに分類しよう。
すると、市松模様に対する処理は、4通りの分類中2つの分類において、矩形領域を反転することに相当する。
あとは、矩形領域の反転を処理するだけである。
これは列方向を座標圧縮したうえで、区間乗算・区間和を取れるSegTreeを使い行方向に黒く塗られたマス数を平面走査していけばよい。
白黒の反転は、区間に-1を掛けることで表現できる。
template<int NV> class SegTree_Lazy { public: vector<ll> val,mul; SegTree_Lazy(){val.resize(NV*2,0); mul.resize(NV*2,1);}; ll reverse(int x,int y,int l=0,int r=NV,int k=1) { if(x<=l && r<=y) { mul[k]*=-1; } else if(l < y && x < r) { val[k]=reverse(x,y,l,(l+r)/2,k*2)+reverse(x,y,(l+r)/2,r,k*2+1); } return val[k]*mul[k]; } }; SegTree_Lazy<1<<20> st; int N; int H[101010][4],W[101010][4],R[101010][4],C[101010][4]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N; FOR(i,N) { int h,w; cin>>h>>w>>y>>x; R[i][(y%2)*2+(x%2)]=y/2; C[i][(y%2)*2+(x%2)]=x/2; H[i][(y%2)*2+(x%2)]=(h+1)/2; W[i][(y%2)*2+(x%2)]=(w+1)/2; R[i][(((y%2)^1)*2+((x%2)^1))]=(y+1)/2; C[i][(((y%2)^1)*2+((x%2)^1))]=(x+1)/2; H[i][(((y%2)^1)*2+((x%2)^1))]=h/2; W[i][(((y%2)^1)*2+((x%2)^1))]=w/2; } ll ret=0; for(int mask=0;mask<4;mask++) { vector<int> cand; map<int,vector<int>> ev; FOR(i,2<<20) st.val[i]=0,st.mul[i]=1; cand.push_back(0); cand.push_back(1<<30); FOR(i,N) if(H[i][mask]&&W[i][mask]) { cand.push_back(C[i][mask]); cand.push_back(C[i][mask]+W[i][mask]); ev[R[i][mask]].push_back(i); ev[R[i][mask]+H[i][mask]].push_back(i); } sort(ALL(cand)); cand.erase(unique(ALL(cand)),cand.end()); FOR(i,cand.size()-1) st.val[i+(1<<20)]=cand[i+1]-cand[i]; for(i=(1<<20)-1;i>=1;i--) st.val[i]=st.val[i*2]+st.val[i*2+1]; ll pre=st.val[1],prey=-1; FORR(e,ev) { ll black=((1LL<<30)-pre)/2; ret+=(e.first-prey)*black; prey=e.first; FORR(i,e.second) { x=lower_bound(ALL(cand),C[i][mask])-cand.begin(); y=lower_bound(ALL(cand),W[i][mask]+C[i][mask])-cand.begin(); pre=st.reverse(x,y); } } } cout<<ret<<endl; }
まとめ
0/1反転の処理を1/-1の処理とみなすと乗算で行えるのでやりやすいね。