牛客集训 湖南省赛E题 Grid 动态开点线段树
国庆牛客集训的题,正好准备好好训练线段树,想起来就补一下。
题意很简单,两种操作行合并或者列合并,每个操作后计算有多少个子块。
这题应该先推导公式,行操作或者列操作只有一种的时候,很简单,总数就是n*m - 有多少行或列合并了*一列多少格子m或者一行多少格子n + 合并的行或者列数。
两种都在,就需要结合图示来理解一下,简单的容斥一下,总数就是n*m - 行列各自算一遍上述的, 再加上行列重叠的,再加上最终行列合成的一块。
然后根据公式,
我们发现这里需要维护的是1~n和m的行或者列,有多少行或者列已经被合并了,n和m最大到1e9,而且不能离散化,(离散化我也不会,😀),又是区间信息合并,就用到了动态开点的线段树。
动态开点的线段树只开我们所需要用到部分,大大节省了空间。
而这里,每次更新ql和qr部分,我只需要用线段树的结点记录区间的长度,最后合并到最大的区间s[1]即可,cnt记录总区间数和新开的左右子树的编号,ls[]和rs[]分别存储左右子树编号,rt除了第一次是0,后来每次更新都是从1开始往下查找的,跟普通线段树一样的初始结点编号。
代码:
1 #include <bits/stdc++.h> 2 #define debug(x) cout << #x << ": " << x << endl 3 using namespace std; 4 typedef long long ll; 5 const int MAXN=2e5+7; 6 const int INF=0x3f3f3f3f; 7 const int MOD=1e9+7; 8 9 struct tree 10 { 11 int s[MAXN],ls[MAXN],rs[MAXN],cnt=0,rt=0; 12 void init() 13 { 14 for(int i=0;i<=cnt;++i) 15 s[i]=ls[i]=rs[i]=0; 16 cnt=rt=0; 17 } 18 void update(int &o,int l,int r,int ql,int qr) 19 { 20 if(!o) o=++cnt; 21 //debug(o); 22 if(s[o]==r-l+1) return; 23 if(ql<=l && r<=qr) {s[o]=r-l+1;return;} 24 int mid=l+r>>1; 25 if(ql<=mid) update(ls[o],l,mid,ql,qr); 26 if(qr>mid) update(rs[o],mid+1,r,ql,qr); 27 s[o]=s[ls[o]]+s[rs[o]]; 28 } 29 }row,col; 30 31 int main() 32 { 33 int n,m,q; 34 while(~scanf("%d%d%d",&n,&m,&q)) 35 { 36 row.init();col.init(); 37 int op,l,r; 38 while(q--) 39 { 40 scanf("%d%d%d",&op,&l,&r); 41 if(op==1) row.update(row.rt,1,n,l,r); 42 else col.update(col.rt,1,m,l,r); 43 ll ans=0; 44 //debug(col.s[1]); 45 //debug(row.s[1]); 46 if(row.s[1]==0) ans=1ll*n*m-1ll*col.s[1]*n+1ll*col.s[1]; 47 else if(col.s[1]==0) ans=1ll*n*m-1ll*row.s[1]*m+1ll*row.s[1]; 48 else ans=1ll*n*m-1ll*col.s[1]*n-1ll*row.s[1]*m+1ll*row.s[1]*col.s[1]+1; 49 printf("%lld\n",ans); 50 } 51 } 52 return 0; 53 }

浙公网安备 33010602011771号